diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..8b2e1a8956 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*.php] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes index 32cfb4c312..66460898b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,8 @@ /scripts/ export-ignore /tests/ export-ignore /www/ export-ignore +/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore +/TESTING.md export-ignore /TROUBLESHOOTING.md export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..3425bfabf6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,39 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + versioning-strategy: "increase" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + labels: + - "cleanup-no-release-required" + - "dependencies" + - "github_actions" + + - package-ecosystem: "npm" + directory: "/www/scripts/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + versioning-strategy: "increase" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + versioning-strategy: "increase" + allow: + - dependency-type: "all" + open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8eed96d41c..0644638553 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: ci +name: CI on: push: @@ -15,16 +15,14 @@ jobs: strategy: matrix: php-version: - - '7.0' - - '7.1' - - '7.2' - - '7.3' - - '7.4' - '8.0' - '8.1' + - '8.2' + - '8.3' + - '8.4' future-release: [false] include: - - php-version: '8.2' + - php-version: '8.5' future-release: true fail-fast: false name: PHP ${{ matrix.php-version }} @@ -37,14 +35,12 @@ jobs: php-version: ${{ matrix.php-version }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 + with: + persist-credentials: false - name: Test - run: bash tests/ci.sh + run: bash tests/run.sh env: CI_PHP_VERSION: ${{ matrix.php-version }} CI_PHP_FUTURE_RELEASE: ${{ matrix.future-release }} - - - name: Static analysis - run: "vendor/bin/psalm" - if: ${{ !matrix.future-release && matrix.php-version >= 7.1 }} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000000..a5ba44bfeb --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,56 @@ +name: Dependabot auto-merge + +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@v2.4.0 + + - name: Approve and label updates + run: | + review_status="$( + gh pr view "${PR_URL}" \ + --json="reviewDecision" \ + --jq=".reviewDecision" + )" + echo "Pull request review status: ${review_status}" + if [[ "${review_status}" != "APPROVED" ]]; then + gh pr review --approve "${PR_URL}" + fi + gh pr edit --add-label "cleanup-no-release-required" "${PR_URL}" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge Dependabot patch updates + if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge Dependabot minor updates + if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor' }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge Dependabot indirect dependency updates + if: ${{ steps.dependabot-metadata.outputs.dependency-type == 'indirect' }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..4dcc53d9bb --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,45 @@ +name: Lint + +on: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Get changed python files + id: changed-files + uses: tj-actions/changed-files@v47 + with: + files: "**/*.py" + + - name: Install requirements + if: steps.changed-files.outputs.any_changed == 'true' + run: pip install black flake8 isort + + - name: black changed files + if: steps.changed-files.outputs.any_changed == 'true' + run: | + black --check --color --diff --quiet ${{ steps.changed-files.outputs.all_changed_files }} + + - name: flake8 changed files + if: steps.changed-files.outputs.any_changed == 'true' + run: | + flake8 --config="tests/setup.cfg" ${{ steps.changed-files.outputs.all_changed_files }} + + - name: isort changed files + if: steps.changed-files.outputs.any_changed == 'true' + run: | + set +e + isort --check-only --force-single-line-imports --profile black ${{ steps.changed-files.outputs.all_changed_files }} + exit_code="${?}" + if [[ "${exit_code}" -ne 0 ]]; then + isort --force-single-line-imports --profile black ${{ steps.changed-files.outputs.all_changed_files }} + git diff --color + exit "${exit_code}" + fi diff --git a/.github/workflows/outdated-track.yml b/.github/workflows/outdated-track.yml new file mode 100644 index 0000000000..93213ca73a --- /dev/null +++ b/.github/workflows/outdated-track.yml @@ -0,0 +1,81 @@ +name: Track outdated dependencies + +on: + schedule: + - cron: '0 18 * * 1-4' + workflow_dispatch: + +jobs: + run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Gather requirements + id: check-outdated + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip + pip install --requirement scripts/make_release_requirements.txt + outdated="$(pip list --outdated --format json | jq)" + outdated_count="$(echo "$outdated" | jq 'length')" + echo "${outdated}" | jq "[{\"outdated_count\":\"${outdated_count}\"}] + ." > scripts/make_release_requirements.json + git diff --color + changes="$(git diff)" + delimiter="$(openssl rand -hex 8)" + echo "changes<<${delimiter}" >> "${GITHUB_OUTPUT}" + echo "${changes}" >> "${GITHUB_OUTPUT}" + echo "${delimiter}" >> "${GITHUB_OUTPUT}" + + - name: Create or update pull request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + commit-message: Update outdated dependencies list + labels: cleanup-no-release-required, dependencies, github_actions + title: ⬆ Update outdated dependencies list + body: | + ```diff + ${{ steps.check-outdated.outputs.changes }} + ``` + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + branch: create-pull-request/outdated + committer: GitHub + delete-branch: true + token: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + push-to-fork: php-curl-class-helper/php-curl-class + env: + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + + - name: Pull request created or updated + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + echo "Pull request: ${{ steps.cpr.outputs.pull-request-url }}" + + - name: Approve outdated dependencies pull request + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + review_status="$( + gh pr view "${PR_URL}" \ + --json="reviewDecision" \ + --jq=".reviewDecision" + )" + echo "Pull request review status: ${review_status}" + if [[ "${review_status}" != "APPROVED" ]]; then + gh pr review --approve "${PR_URL}" + fi + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_TOKEN }} + + - name: Enable auto-merge for outdated dependencies pull request + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/outdated-update-package.yml b/.github/workflows/outdated-update-package.yml new file mode 100644 index 0000000000..967f2b0403 --- /dev/null +++ b/.github/workflows/outdated-update-package.yml @@ -0,0 +1,87 @@ +name: Update outdated package + +on: + workflow_dispatch: + inputs: + package_name: + required: true + type: string + package_version: + required: true + type: string + +jobs: + upgrade: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Install pip-tools + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip pip-tools + + - name: Upgrade outdated package + env: + PACKAGE_NAME: ${{ github.event.inputs.package_name }} + PACKAGE_VERSION: ${{ github.event.inputs.package_version }} + run: | + source venv/bin/activate + + set -x + echo "Upgrading ${PACKAGE_NAME} to version ${PACKAGE_VERSION}" + upgrade_package="${PACKAGE_NAME}==${PACKAGE_VERSION}" + pip-compile \ + --upgrade-package="${upgrade_package}" \ + --output-file="scripts/make_release_requirements.txt" \ + "scripts/make_release_requirements.in" + + - name: Create or update pull request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "Update package ${{ github.event.inputs.package_name }}" + labels: cleanup-no-release-required, dependencies, github_actions + title: ⬆ Update ${{ github.event.inputs.package_name }} + body: "Updating ${{ github.event.inputs.package_name }}" + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + branch: "create-pull-request/upgrade-${{ github.event.inputs.package_name }}" + committer: GitHub + delete-branch: true + token: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + push-to-fork: php-curl-class-helper/php-curl-class + env: + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + + - name: Pull request created or updated + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + echo "Pull request: ${{ steps.cpr.outputs.pull-request-url }}" + + - name: Enable auto-merge for pull request + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Approve update + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + review_status="$( + gh pr view "${PR_URL}" \ + --json="reviewDecision" \ + --jq=".reviewDecision" + )" + echo "Pull request review status: ${review_status}" + if [[ "${review_status}" != "APPROVED" ]]; then + gh pr review --approve "${PR_URL}" + fi + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_TOKEN }} diff --git a/.github/workflows/outdated-update-packages.yml b/.github/workflows/outdated-update-packages.yml new file mode 100644 index 0000000000..18dc7288c8 --- /dev/null +++ b/.github/workflows/outdated-update-packages.yml @@ -0,0 +1,40 @@ +name: Update outdated packages + +on: + schedule: + - cron: '0 20 * * 1-4' + workflow_dispatch: + +jobs: + update-dependencies: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Upgrade outdated packages + run: | + items=($( + jq -r '.[1:]' "scripts/make_release_requirements.json" | + jq -r '.[] | "\(.name)|\(.version)|\(.latest_version)"' + )) + for item in "${items[@]}"; do + IFS='|' read -r "package_name" "package_version" "package_latest_version" <<< "${item}" + echo "found outdated package:" + echo "name: ${package_name}" + echo "version: ${package_version}" + echo "latest_version: ${package_latest_version}" + curl \ + --request "POST" \ + --silent \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${TOKEN}" \ + "https://api.github.com/repos/php-curl-class/php-curl-class/actions/workflows/outdated-update-package.yml/dispatches" \ + --data "{\"ref\":\"master\",\"inputs\":{\"package_name\":\"${package_name}\",\"package_version\":\"${package_latest_version}\"}}" + done + + echo "done" + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pre-commit-auto-update.yml b/.github/workflows/pre-commit-auto-update.yml new file mode 100644 index 0000000000..93762d663d --- /dev/null +++ b/.github/workflows/pre-commit-auto-update.yml @@ -0,0 +1,82 @@ +name: Pre-commit auto-update + +on: + schedule: + - cron: '0 18 * * 1-4' + workflow_dispatch: + +jobs: + update-pre-commit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: 'master' + persist-credentials: false + + - name: Update dependencies + run: | + set -x + python -m pip install --upgrade pip + pip install pre-commit + pre-commit autoupdate --config="tests/.pre-commit-config.yaml" + + - name: Gather changes + id: gather-changes + run: | + git diff --color + changes="$(git diff)" + delimiter="$(openssl rand -hex 8)" + echo "changes<<${delimiter}" >> "${GITHUB_OUTPUT}" + echo "${changes}" >> "${GITHUB_OUTPUT}" + echo "${delimiter}" >> "${GITHUB_OUTPUT}" + + - name: Create or update pull request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + commit-message: Update pre-commit dependencies + labels: cleanup-no-release-required, dependencies, github_actions + title: ⬆ Update pre-commit dependencies + body: | + ```diff + ${{ steps.gather-changes.outputs.changes }} + ``` + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + branch: create-pull-request/pre-commit + committer: GitHub + delete-branch: true + token: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + push-to-fork: php-curl-class-helper/php-curl-class + env: + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_BOT_TOKEN }} + + - name: Pull request created or updated + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + echo "Pull request: ${{ steps.cpr.outputs.pull-request-url }}" + + - name: Enable auto-merge for pull request + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + gh pr merge --auto --merge "${PR_URL}" + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Approve update + if: ${{ steps.cpr.outputs.pull-request-number }} + run: | + review_status="$( + gh pr view "${PR_URL}" \ + --json="reviewDecision" \ + --jq=".reviewDecision" + )" + echo "Pull request review status: ${review_status}" + if [[ "${review_status}" != "APPROVED" ]]; then + gh pr review --approve "${PR_URL}" + fi + env: + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..5620c5b19c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Release + +on: + schedule: + - cron: '0 18 * * 1-4' + workflow_dispatch: + +jobs: + release: + # Prevent this workflow from running elsewhere. + if: github.repository_owner == 'php-curl-class' + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install requirements + run: | + python -m pip install --upgrade pip + pip install --requirement scripts/make_release_requirements.txt + + - name: Set git details + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Make release + run: python scripts/make_release.py + env: + GITHUB_TOKEN: ${{ secrets.PHP_CURL_CLASS_TOKEN }} diff --git a/.gitignore b/.gitignore index f054ad8514..e4e24c16cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ vendor/ +venv/ composer.lock .idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e6943782a..4c50d3d044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,249 @@ PHP Curl Class uses semantic versioning with version numbers written as `MAJOR.M `MINOR` and `PATCH` version changes. It is recommended to review `MAJOR` changes prior to upgrade as there may be backwards-incompatible changes that will affect existing usage. + + +## 12.0.1 - 2025-11-03 + +- Remove calls to deprecated functions ([#1005](https://github.com/php-curl-class/php-curl-class/pull/1005)) + +## 12.0.0 - 2025-03-25 + +- Drop support for PHP 7.4 ([#937](https://github.com/php-curl-class/php-curl-class/pull/937)) + +## 11.1.0 - 2025-03-24 + +- Add methods like Curl::setGet() for each HTTP request method ([#936](https://github.com/php-curl-class/php-curl-class/pull/936)) + +## 11.0.5 - 2025-03-11 + +- Fix PHPStan static analysis errors ([#929](https://github.com/php-curl-class/php-curl-class/pull/929)) + +## 11.0.4 - 2025-02-20 + +- Increase psalm strictness ([#925](https://github.com/php-curl-class/php-curl-class/pull/925)) + +## 11.0.3 - 2025-02-19 + +- Use default for Psalm ensureOverrideAttribute ([#923](https://github.com/php-curl-class/php-curl-class/pull/923)) + +## 11.0.2 - 2025-02-18 + +- Fix CI (PHPUnit) ([#918](https://github.com/php-curl-class/php-curl-class/pull/918)) + +## 11.0.1 - 2025-01-13 + +- Increase Psalm strictness ([#909](https://github.com/php-curl-class/php-curl-class/pull/909)) +- Increase PHPStan strictness ([#908](https://github.com/php-curl-class/php-curl-class/pull/908)) + +## 11.0.0 - 2024-08-22 + +- Drop support for PHP 7.3 ([#889](https://github.com/php-curl-class/php-curl-class/pull/889)) +- Drop support for PHP 7.2 ([#888](https://github.com/php-curl-class/php-curl-class/pull/888)) +- Drop support for PHP 7.1 ([#887](https://github.com/php-curl-class/php-curl-class/pull/887)) + +## 10.0.1 - 2024-08-21 + +- Use nullable type declaration ([#882](https://github.com/php-curl-class/php-curl-class/pull/882)) + +## 10.0.0 - 2024-08-20 + +- Drop support for PHP 7.0 ([#880](https://github.com/php-curl-class/php-curl-class/pull/880)) +- Add public method getActiveCurls ([#871](https://github.com/php-curl-class/php-curl-class/pull/871)) + +## 9.19.2 - 2024-04-09 + +- Fix CI: Use nullable type declaration ([#859](https://github.com/php-curl-class/php-curl-class/pull/859)) + +## 9.19.1 - 2024-02-27 + +- Fix afterSend not being called ([#848](https://github.com/php-curl-class/php-curl-class/pull/848)) + +## 9.19.0 - 2024-01-18 + +- Allow displaying curl option value without specifying value ([#837](https://github.com/php-curl-class/php-curl-class/pull/837)) + +## 9.18.2 - 2023-09-11 + +- Fix use of mb_strpos() causing error when polyfill is used ([#813](https://github.com/php-curl-class/php-curl-class/pull/813)) + +## 9.18.1 - 2023-08-29 + +- Add additional check for decoding gzip-encoded responses ([#808](https://github.com/php-curl-class/php-curl-class/pull/808)) + +## 9.18.0 - 2023-08-28 + +- Implement Curl::setError() and MultiCurl::setError() ([#805](https://github.com/php-curl-class/php-curl-class/pull/805)) +- Rename ::setError() to ::afterSend() ([#807](https://github.com/php-curl-class/php-curl-class/pull/807)) + +## 9.17.4 - 2023-07-10 + +- Add coding standards rule to use the null coalescing operator ?? where possible ([#801](https://github.com/php-curl-class/php-curl-class/pull/801)) +- Replace isset with null coalescing operator ([#800](https://github.com/php-curl-class/php-curl-class/pull/800)) + +## 9.17.3 - 2023-07-04 + +- Update PHP_CodeSniffer ruleset: PSR2 → PSR12 ([#797](https://github.com/php-curl-class/php-curl-class/pull/797)) +- Add additional coding standard checks ([#796](https://github.com/php-curl-class/php-curl-class/pull/796)) + +## 9.17.2 - 2023-06-27 + +- Use short array syntax ([#793](https://github.com/php-curl-class/php-curl-class/pull/793)) +- Add PHP-CS-Fixer to check for unused imports ([#794](https://github.com/php-curl-class/php-curl-class/pull/794)) +- Replace `uniqid` by `random_bytes` ([#792](https://github.com/php-curl-class/php-curl-class/pull/792)) + +## 9.17.1 - 2023-06-14 + +- Improve and add tests for Curl::fastDownload() ([#791](https://github.com/php-curl-class/php-curl-class/pull/791)) + +## 9.17.0 - 2023-06-13 + +- Make method to display curl option value public ([#790](https://github.com/php-curl-class/php-curl-class/pull/790)) + +## 9.16.1 - 2023-06-12 + +- Differentiate between internal options and user-set options ([#788](https://github.com/php-curl-class/php-curl-class/pull/788)) +- Create method to display a curl option value ([#785](https://github.com/php-curl-class/php-curl-class/pull/785)) +- Fix existing header overwritten after using MultiCurl::addCurl() ([#787](https://github.com/php-curl-class/php-curl-class/pull/787)) + +## 9.16.0 - 2023-05-25 + +- Graduate Curl::fastDownload() ([#783](https://github.com/php-curl-class/php-curl-class/pull/783)) + +## 9.15.1 - 2023-05-24 + +- Fix PHP CodeSniffer errors ([#782](https://github.com/php-curl-class/php-curl-class/pull/782)) + +## 9.15.0 - 2023-05-22 + +- Update Curl::diagnose() to detect bit flags with negative values ([#781](https://github.com/php-curl-class/php-curl-class/pull/781)) +- Display bit flags in use when calling Curl::diagnose() ([#779](https://github.com/php-curl-class/php-curl-class/pull/779)) + +## 9.14.5 - 2023-05-16 + +- Handle missing content-type response header in Curl::diagnose() ([#778](https://github.com/php-curl-class/php-curl-class/pull/778)) + +## 9.14.4 - 2023-05-08 + +- Update article in Curl::diagnose() Allow header warning ([#776](https://github.com/php-curl-class/php-curl-class/pull/776)) + +## 9.14.3 - 2023-03-13 + +- Remove use of array_merge() inside loop ([#774](https://github.com/php-curl-class/php-curl-class/pull/774)) + +## 9.14.2 - 2023-03-09 + +- Clean up: Reduce nesting ([#771](https://github.com/php-curl-class/php-curl-class/pull/771)) + +## 9.14.1 - 2023-02-27 + +- Remove coding standard ruleset exclusion ([#768](https://github.com/php-curl-class/php-curl-class/pull/768)) + +## 9.14.0 - 2023-02-26 + +- Make https:// and http:// the allowed request protocols by default ([#767](https://github.com/php-curl-class/php-curl-class/pull/767)) + +## 9.13.1 - 2023-01-16 + +- Allow uploads with CURLStringFile type ([#762](https://github.com/php-curl-class/php-curl-class/pull/762)) + +## 9.13.0 - 2023-01-13 + +- Implement abstract class BaseCurl for Curl and MultiCurl ([#759](https://github.com/php-curl-class/php-curl-class/pull/759)) +- Display error messages found in Curl::diagnose() ([#758](https://github.com/php-curl-class/php-curl-class/pull/758)) +- Fix Curl::diagnose() request type output for POST requests ([#757](https://github.com/php-curl-class/php-curl-class/pull/757)) + +## 9.12.6 - 2023-01-11 + +- Replace use of #[\AllowDynamicProperties] ([#756](https://github.com/php-curl-class/php-curl-class/pull/756)) +- silence PHP 8.2 deprecation notices ([#754](https://github.com/php-curl-class/php-curl-class/pull/754)) + +## 9.12.5 - 2022-12-20 + +- Fix static analysis error ([#752](https://github.com/php-curl-class/php-curl-class/pull/752)) + +## 9.12.4 - 2022-12-17 + +- Exclude additional files from git archive ([#751](https://github.com/php-curl-class/php-curl-class/pull/751)) + +## 9.12.3 - 2022-12-13 + +- Ensure string response before gzip decode ([#749](https://github.com/php-curl-class/php-curl-class/pull/749)) + +## 9.12.2 - 2022-12-11 + +- Disable warning when gzip-decoding response errors ([#748](https://github.com/php-curl-class/php-curl-class/pull/748)) + +## 9.12.1 - 2022-12-08 + +- Include option constant that uses the CURLINFO_ prefix ([#745](https://github.com/php-curl-class/php-curl-class/pull/745)) + +## 9.12.0 - 2022-12-07 + +- Add automatic gzip decoding of response ([#744](https://github.com/php-curl-class/php-curl-class/pull/744)) + +## 9.11.1 - 2022-12-06 + +- change: remove unused namespace import ([#743](https://github.com/php-curl-class/php-curl-class/pull/743)) + +## 9.11.0 - 2022-12-05 + +- Add Curl::diagnose() HTTP method check matches methods allowed ([#741](https://github.com/php-curl-class/php-curl-class/pull/741)) +- Add temporary fix missing template params ([#742](https://github.com/php-curl-class/php-curl-class/pull/742)) + +## 9.10.0 - 2022-11-07 + +- Display request options in Curl::diagnose() output ([#739](https://github.com/php-curl-class/php-curl-class/pull/739)) + +## 9.9.0 - 2022-11-06 + +- Fix MultiCurl::setCookieString() ([#738](https://github.com/php-curl-class/php-curl-class/pull/738)) +- Pass MultiCurl options to new Curl instances earlier ([#737](https://github.com/php-curl-class/php-curl-class/pull/737)) +- Add deferred constant curlErrorCodeConstants ([#736](https://github.com/php-curl-class/php-curl-class/pull/736)) + +## 9.8.0 - 2022-10-01 + +- Include curl error code constant in curl error message ([#733](https://github.com/php-curl-class/php-curl-class/pull/733)) + +## 9.7.0 - 2022-09-29 + +- Implement ArrayUtil::arrayRandomIndex() ([#732](https://github.com/php-curl-class/php-curl-class/pull/732)) + +## 9.6.3 - 2022-09-24 + +- Remove filter flag constants deprecated as of PHP 7.3 ([#730](https://github.com/php-curl-class/php-curl-class/pull/730)) + +## 9.6.2 - 2022-09-24 + +- Call MultiCurl::beforeSend() before each request is made ([#723](https://github.com/php-curl-class/php-curl-class/pull/723)) +- Encode keys for post data with numeric keys ([#726](https://github.com/php-curl-class/php-curl-class/pull/726)) +- Fix building post data with object ([#728](https://github.com/php-curl-class/php-curl-class/pull/728)) + +## 9.6.1 - 2022-06-30 + +### Fixed + +- Attempt to stop active requests when `MultiCurl::stop()` is called + [#714](https://github.com/php-curl-class/php-curl-class/issues/714) + [#718](https://github.com/php-curl-class/php-curl-class/issues/718) +- Retain keys for arrays with null values when building post data + [#712](https://github.com/php-curl-class/php-curl-class/issues/712) + +## 9.6.0 - 2022-03-17 + +### Added + +- Method `MultiCurl::stop()` for stopping subsequent requests + [#708](https://github.com/php-curl-class/php-curl-class/issues/708) + +## 9.5.1 - 2021-12-14 + +### Fixed + +- Silence PHP 8.1 deprecations [#691](https://github.com/php-curl-class/php-curl-class/issues/691) +- Remove data parameter from additional request types + [#689](https://github.com/php-curl-class/php-curl-class/issues/689) + ## 9.5.0 - 2021-11-21 ### Added diff --git a/README.md b/README.md index bda18bfdea..d78203ed03 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # PHP Curl Class: HTTP requests made easy -[![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg?style=flat-square&sort=semver)](https://github.com/php-curl-class/php-curl-class/releases/) -[![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg?style=flat-square)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) -[![](https://img.shields.io/github/workflow/status/php-curl-class/php-curl-class/ci?style=flat-square)](https://github.com/php-curl-class/php-curl-class/actions/workflows/ci.yml) -[![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg?style=flat-square)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg?style=for-the-badge&sort=semver)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg?style=for-the-badge)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) +[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/ci.yml?style=for-the-badge&label=build&branch=master)](https://github.com/php-curl-class/php-curl-class/actions/workflows/ci.yml) +[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/release.yml?style=for-the-badge&label=release&branch=master)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/dependabot/dependabot-updates?style=for-the-badge&label=Dependabot&branch=master)](https://github.com/php-curl-class/php-curl-class/actions/workflows/dependabot/dependabot-updates) +[![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg?style=for-the-badge)](https://github.com/php-curl-class/php-curl-class/releases/) PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. @@ -11,32 +13,34 @@ PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. --- -- [Installation](#installation) -- [Requirements](#requirements) -- [Quick Start and Examples](#quick-start-and-examples) -- [Available Methods](#available-methods) -- [Security](#security) -- [Troubleshooting](#troubleshooting) -- [Run Tests](#run-tests) -- [Contribute](#contribute) +- [⚙️ Installation](#%EF%B8%8F-installation) +- [📋 Requirements](#-requirements) +- [🚀 Quick Start and Examples](#-quick-start-and-examples) +- [📖 Available Methods](#-available-methods) +- [🔒 Security](#-security) +- [🛠️ Troubleshooting](#%EF%B8%8F-troubleshooting) +- [🧪 Testing](#-testing) +- [🤝 Contributing](#-contributing) --- -### Installation +### ⚙️ Installation -To install PHP Curl Class, simply: +To install PHP Curl Class, run the following command: - $ composer require php-curl-class/php-curl-class + composer require php-curl-class/php-curl-class -For latest commit version: +To install the latest commit version: - $ composer require php-curl-class/php-curl-class @dev + composer require php-curl-class/php-curl-class @dev -### Requirements +Installation instructions to use the `composer` command can be found on https://github.com/composer/composer. -PHP Curl Class works with PHP 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, and 8.1. +### 📋 Requirements -### Quick Start and Examples +PHP Curl Class works with PHP 8.4, 8.3, 8.2, 8.1, and 8.0. + +### 🚀 Quick Start and Examples More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). @@ -49,7 +53,8 @@ $curl = new Curl(); $curl->get('https://www.example.com/'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; + $curl->diagnose(); } else { echo 'Response:' . "\n"; var_dump($curl->response); @@ -82,7 +87,7 @@ $curl->setCookie('key', 'value'); $curl->get('https://www.example.com/'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); @@ -150,7 +155,6 @@ $curl->close(); ```php // Example access to curl object. curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); -curl_close($curl->curl); ``` ```php @@ -190,12 +194,13 @@ $multi_curl->start(); // Blocks until all items in the queue have been processed More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). -### Available Methods +### 📖 Available Methods ```php -Curl::__construct($base_url = null) +Curl::__construct($base_url = null, $options = []) Curl::__destruct() Curl::__get($name) -Curl::_fastDownload($url, $filename, $connections = 4) { +Curl::__isset($name) +Curl::afterSend($callback) Curl::attemptRetry() Curl::beforeSend($callback) Curl::buildPostData($data) @@ -205,10 +210,12 @@ Curl::complete($callback) Curl::delete($url, $query_parameters = [], $data = []) Curl::diagnose($return = false) Curl::disableTimeout() +Curl::displayCurlOptionValue($option, $value = null) Curl::download($url, $mixed_filename) Curl::error($callback) Curl::exec($ch = null) Curl::execDone() +Curl::fastDownload($url, $filename, $connections = 4) Curl::get($url, $data = []) Curl::getAttempts() Curl::getBeforeSendCallback() @@ -229,6 +236,7 @@ Curl::getId() Curl::getInfo($opt = null) Curl::getJsonDecoder() Curl::getOpt($option) +Curl::getOptions() Curl::getRawResponse() Curl::getRawResponseHeaders() Curl::getRemainingRetries() @@ -241,6 +249,7 @@ Curl::getRetries() Curl::getRetryDecider() Curl::getSuccessCallback() Curl::getUrl() +Curl::getUserSetOptions() Curl::getXmlDecoder() Curl::head($url, $data = []) Curl::isChildOfMultiCurl() @@ -265,14 +274,18 @@ Curl::setCookieJar($cookie_jar) Curl::setCookieString($string) Curl::setCookies($cookies) Curl::setDefaultDecoder($mixed = 'json') +Curl::setDefaultHeaderOut() Curl::setDefaultJsonDecoder() Curl::setDefaultTimeout() Curl::setDefaultUserAgent() Curl::setDefaultXmlDecoder() +Curl::setDelete($url, $query_parameters = [], $data = []) Curl::setDigestAuthentication($username, $password = '') Curl::setFile($file) Curl::setFollowLocation($follow_location = true) Curl::setForbidReuse($forbid_reuse = true) +Curl::setGet($url, $data = []) +Curl::setHead($url, $data = []) Curl::setHeader($key, $value) Curl::setHeaders($headers) Curl::setInterface($interface) @@ -280,21 +293,29 @@ Curl::setJsonDecoder($mixed) Curl::setMaxFilesize($bytes) Curl::setMaximumRedirects($maximum_redirects) Curl::setOpt($option, $value) +Curl::setOptions($url, $data = []) Curl::setOpts($options) +Curl::setPatch($url, $data = []) Curl::setPort($port) +Curl::setPost($url, $data = '', $follow_303_with_post = false) +Curl::setProtocols($protocols) Curl::setProxy($proxy, $port = null, $username = null, $password = null) Curl::setProxyAuth($auth) Curl::setProxyTunnel($tunnel = true) Curl::setProxyType($type) +Curl::setPut($url, $data = []) Curl::setRange($range) +Curl::setRedirectProtocols($redirect_protocols) Curl::setReferer($referer) Curl::setReferrer($referrer) Curl::setRetry($mixed) -Curl::setStop($callback) +Curl::setSearch($url, $data = []) +Curl::setStop($callback = null) Curl::setTimeout($seconds) Curl::setUrl($url, $mixed_data = '') Curl::setUserAgent($user_agent) Curl::setXmlDecoder($mixed) +Curl::stop() Curl::success($callback) Curl::unsetHeader($key) Curl::unsetProxy() @@ -311,11 +332,13 @@ MultiCurl::addPatch($url, $data = []) MultiCurl::addPost($url, $data = '', $follow_303_with_post = false) MultiCurl::addPut($url, $data = []) MultiCurl::addSearch($url, $data = []) +MultiCurl::afterSend($callback) MultiCurl::beforeSend($callback) MultiCurl::close() MultiCurl::complete($callback) MultiCurl::disableTimeout() MultiCurl::error($callback) +MultiCurl::getActiveCurls() MultiCurl::getOpt($option) MultiCurl::removeHeader($key) MultiCurl::setAutoReferer($auto_referer = true) @@ -349,49 +372,33 @@ MultiCurl::setRange($range) MultiCurl::setRateLimit($rate_limit) MultiCurl::setReferer($referer) MultiCurl::setReferrer($referrer) +MultiCurl::setRequestTimeAccuracy() MultiCurl::setRetry($mixed) MultiCurl::setTimeout($seconds) MultiCurl::setUrl($url, $mixed_data = '') MultiCurl::setUserAgent($user_agent) MultiCurl::setXmlDecoder($mixed) MultiCurl::start() +MultiCurl::stop() MultiCurl::success($callback) MultiCurl::unsetHeader($key) MultiCurl::unsetProxy() -MultiCurl::verbose($on = true, $output = STDERR) +MultiCurl::verbose($on = true, $output = 'STDERR') ``` -### Security +### 🔒 Security See [SECURITY](https://github.com/php-curl-class/php-curl-class/blob/master/SECURITY.md) for security considerations. -### Troubleshooting - -See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for troubleshooting. - -### Run Tests - -To run tests: - - $ git clone https://github.com/php-curl-class/php-curl-class.git - $ cd php-curl-class/ - $ composer update - $ ./tests/run.sh - -To run select tests: +### 🛠️ Troubleshooting - $ git clone https://github.com/php-curl-class/php-curl-class.git - $ cd php-curl-class/ - $ composer update - $ ./tests/run.sh --filter=keyword +See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for help troubleshooting. -To test all PHP versions in containers: +### 🧪 Testing - $ git clone https://github.com/php-curl-class/php-curl-class.git - $ cd php-curl-class/ - $ ./tests/test_all.sh +See [TESTING](https://github.com/php-curl-class/php-curl-class/blob/master/TESTING.md) for testing information. -### Contribute +### 🤝 Contributing 1. Check for open issues or open a new issue to start a discussion around a bug or feature. 1. Fork the repository on GitHub to start making your changes. diff --git a/SECURITY.md b/SECURITY.md index e228edd2c3..08628a7b23 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -22,7 +22,7 @@ Safer: ```php function is_allowed_url(http://github.com/$url,%20$allowed_url_schemes%20=%20['http',%20'https']) { - $valid_url = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) !== false; + $valid_url = filter_var($url, FILTER_VALIDATE_URL) !== false; if ($valid_url) { $scheme = parse_url(http://github.com/$url,%20PHP_URL_SCHEME); return in_array($scheme, $allowed_url_schemes, true); @@ -35,6 +35,11 @@ $url = $_GET['url']; if (!is_allowed_url(http://github.com/$url)) { die('Unsafe url detected.'); } + +$curl = new Curl(); +$curl->setProtocols(CURLPROTO_HTTPS); +$curl->setRedirectProtocols(CURLPROTO_HTTPS); +$curl->get($url); ``` ### Url may point to internal urls @@ -42,6 +47,20 @@ if (!is_allowed_url(http://github.com/$url)) { * Url may point to internal urls including those behind a firewall (e.g. http://192.168.0.1/ or ftp://192.168.0.1/). Use a whitelist to allow certain urls rather than a blacklist. +* Use `Curl::setProtocols()` and `Curl::setRedirectProtocols()` to restrict allowed protocols. + +```php +// Allow only HTTPS protocols. +$curl->setProtocols(CURLPROTO_HTTPS); +$curl->setRedirectProtocols(CURLPROTO_HTTPS); +``` + +```php +// Allow HTTPS and HTTP protocols. +$curl->setProtocols(CURLPROTO_HTTPS | CURLPROTO_HTTP); +$curl->setRedirectProtocols(CURLPROTO_HTTPS | CURLPROTO_HTTP); +``` + ### Request data may refer to system files * Request data prefixed with the `@` character may have special interpretation and read from system files. diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000000..aeaf8f627a --- /dev/null +++ b/TESTING.md @@ -0,0 +1,33 @@ +# Testing + +### Run Tests Locally + +To run tests: + +```bash +git clone https://github.com/php-curl-class/php-curl-class.git +cd php-curl-class/ +composer update +./tests/run.sh +``` + +To run select tests: + +```bash +git clone https://github.com/php-curl-class/php-curl-class.git +cd php-curl-class/ +composer update +./tests/run.sh --filter=keyword +``` + +To test all PHP versions in containers: + +```bash +git clone https://github.com/php-curl-class/php-curl-class.git +cd php-curl-class/ +./tests/test_all.sh +``` + +### Continuous Integration Tests + +Continuous integration runs [tests/run.sh](https://github.com/php-curl-class/php-curl-class/blob/master/tests/run.sh) on supported PHP versions and is configured with [.github/workflows/ci.yml](https://github.com/php-curl-class/php-curl-class/blob/master/.github/workflows/ci.yml). diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index f4cc50e9fb..3b4cb9f2ed 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -5,7 +5,7 @@ ```php $curl = new Curl(); $curl->get('https://www.example.com/'); -$curl->diagnose(); // <-- HERE +$curl->diagnose(); // ← HERE ``` ### Debug the entire curl instance @@ -13,7 +13,7 @@ $curl->diagnose(); // <-- HERE ```php $curl = new Curl(); $curl->get('https://www.example.com/'); -var_dump($curl); // <-- HERE +var_dump($curl); // ← HERE ``` ### Ensure you have the latest version of the library installed @@ -48,12 +48,12 @@ $curl->get('https://www.example.com/'); echo 'error: ' . $curl->error . "\n"; echo 'errorCode: ' . $curl->errorCode . "\n"; echo 'errorMessage: ' . $curl->errorMessage . "\n"; -echo 'curlError: ' . $curlError . "\n"; -echo 'curlErrorCode: ' . $curlErrorCode . "\n"; -echo 'curlErrorMessage: ' . $curlErrorMessage . "\n"; -echo 'httpError: ' . $httpError . "\n"; -echo 'httpStatusCode: ' . $httpStatusCode . "\n"; -echo 'httpErrorMessage: ' . $httpErrorMessage . "\n"; +echo 'curlError: ' . $curl->error . "\n"; +echo 'curlErrorCode: ' . $curl->errorCode . "\n"; +echo 'curlErrorMessage: ' . $curl->errorMessage . "\n"; +echo 'httpError: ' . $curl->httpError . "\n"; +echo 'httpStatusCode: ' . $curl->httpStatusCode . "\n"; +echo 'httpErrorMessage: ' . $curl->httpErrorMessage . "\n"; echo 'requestHeaders:' . "\n"; var_dump($curl->requestHeaders); echo 'responseHeaders:' . "\n"; diff --git a/composer.json b/composer.json index 1f54f83521..37e568954a 100644 --- a/composer.json +++ b/composer.json @@ -11,18 +11,25 @@ "authors": [ { "name": "Zach Borboa" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-curl-class/php-curl-class/graphs/contributors" } ], "require": { - "php": ">=7.0", + "php": ">=8.0", "ext-curl": "*" }, "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "*", "ext-gd": "*", - "phpcompatibility/php-compatibility": "*", + "friendsofphp/php-cs-fixer": "*", + "phpcompatibility/php-compatibility": "dev-develop", + "phpcsstandards/phpcsutils": "@alpha", + "phpstan/phpstan": "*", "phpunit/phpunit": "*", - "squizlabs/php_codesniffer": "*", - "vimeo/psalm": "*" + "squizlabs/php_codesniffer": "*" }, "suggest": { "ext-mbstring": "*" @@ -31,5 +38,16 @@ "psr-4": { "Curl\\": "src/Curl/" } + }, + "autoload-dev": { + "files": [ + "./tests/Helper.php", + "./tests/User.php" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/examples/before_send_retry.php b/examples/before_send_retry.php new file mode 100644 index 0000000000..9be2c6ee6e --- /dev/null +++ b/examples/before_send_retry.php @@ -0,0 +1,27 @@ +setRetry($max_retries); + +$curl->beforeSend(function ($instance) { + echo 'current attempts: ' . $instance->attempts . "\n"; + echo 'current retries: ' . $instance->retries . "\n"; + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$curl->get('https://httpbin.org/status/503'); + +if ($curl->error) { + echo 'Error: ' . $curl->errorMessage . "\n"; + echo 'final attempts: ' . $curl->attempts . "\n"; + echo 'final retries: ' . $curl->retries . "\n"; +} else { + echo 'Response:' . "\n"; + var_dump($curl->response); +} diff --git a/examples/coinbase_account_balance.php b/examples/coinbase_account_balance.php index 45e9b2201a..23d7a9f435 100644 --- a/examples/coinbase_account_balance.php +++ b/examples/coinbase_account_balance.php @@ -1,4 +1,5 @@ setRetry($max_retries); + +$curl->beforeSend(function ($instance) { + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$curl->error(function ($instance) { + echo 'not lucky this round' . "\n"; +}); + +$curl->success(function ($instance) { + echo + 'success!' . "\n" . + 'got number ' . $instance->response->args->number . ' ' . + 'after ' . $instance->attempts . ' attempt(s).' . "\n"; +}); + +$curl->afterSend(function ($instance) { + $random_number = (int)$instance->response->args->number; + $lucky = $random_number === 7; + $instance->error = !$lucky; + + if (!$lucky) { + $instance->setUrl('https://httpbin.org/get?number=' . random_int(0, 10)); + } +}); + +$curl->get('https://httpbin.org/get?number=' . random_int(0, 10)); + +// $ php curl_after_send.php +// about to make request to https://httpbin.org/get?number=3 +// about to make request to https://httpbin.org/get?number=1 +// about to make request to https://httpbin.org/get?number=7 +// success! +// got number 7 after 3 attempt(s). diff --git a/examples/curl_display_curl_option_value.php b/examples/curl_display_curl_option_value.php new file mode 100644 index 0000000000..b6f9b554ed --- /dev/null +++ b/examples/curl_display_curl_option_value.php @@ -0,0 +1,17 @@ +verbose(); + +$curl->displayCurlOptionValue(CURLOPT_VERBOSE); +// "CURLOPT_VERBOSE: true". + +$curl->displayCurlOptionValue(41); +// "CURLOPT_VERBOSE: true". + +$curl->displayCurlOptionValue(CURLOPT_PROTOCOLS); +// "CURLOPT_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS)". diff --git a/examples/curl_display_curl_option_values.php b/examples/curl_display_curl_option_values.php new file mode 100644 index 0000000000..49715487d6 --- /dev/null +++ b/examples/curl_display_curl_option_values.php @@ -0,0 +1,42 @@ +setUserAgent('some agent'); +$curl->setTimeout(60); + +foreach ($curl->getOptions() as $option => $value) { + echo 'option ' . $option . ':' . "\n"; + $curl->displayCurlOptionValue($option, $value); + echo "\n"; +} + +// option 181: +// CURLOPT_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS) +// +// option 182: +// CURLOPT_REDIR_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS) +// +// option 10018: +// CURLOPT_USERAGENT: "some agent" +// +// option 13: +// CURLOPT_TIMEOUT: 60 +// +// option 2: +// CURLINFO_HEADER_OUT: true +// +// option 20056: +// CURLOPT_PROGRESSFUNCTION: (callable) +// +// option 43: +// CURLOPT_NOPROGRESS: false +// +// option 20079: +// CURLOPT_HEADERFUNCTION: (callable) +// +// option 19913: +// CURLOPT_RETURNTRANSFER: true diff --git a/examples/curl_display_curl_option_values_user_set.php b/examples/curl_display_curl_option_values_user_set.php new file mode 100644 index 0000000000..63839423eb --- /dev/null +++ b/examples/curl_display_curl_option_values_user_set.php @@ -0,0 +1,21 @@ +setUserAgent('some agent'); +$curl->setTimeout(60); + +foreach ($curl->getUserSetOptions() as $option => $value) { + echo 'user set option ' . $option . ':' . "\n"; + $curl->displayCurlOptionValue($option, $value); + echo "\n"; +} + +// user set option 10018: +// CURLOPT_USERAGENT: "some agent" +// +// user set option 13: +// CURLOPT_TIMEOUT: 60 diff --git a/examples/progress.php b/examples/curl_progress.php similarity index 80% rename from examples/progress.php rename to examples/curl_progress.php index b8e67a4376..84495759bc 100644 --- a/examples/progress.php +++ b/examples/curl_progress.php @@ -1,4 +1,5 @@ download('https://www.php.net/distributions/manual/php_manual_en.html.gz', '/tmp/php_manual_en.html.gz'); + +if ($curl->error) { + echo 'Download error: ' . $curl->errorMessage . "\n"; +} else { + echo 'Download complete' . "\n"; +} diff --git a/examples/progress_advanced.php b/examples/curl_progress_advanced.php similarity index 84% rename from examples/progress_advanced.php rename to examples/curl_progress_advanced.php index 7b47af7888..8fd1a66baf 100644 --- a/examples/progress_advanced.php +++ b/examples/curl_progress_advanced.php @@ -1,4 +1,5 @@ complete(function ($instance) { - echo "\n" . 'download complete' . "\n"; + if ($instance->error) { + echo "\n" . 'Download error: ' . $instance->errorMessage . "\n"; + } else { + echo "\n" . 'Download complete' . "\n"; + } }); $curl->download('https://www.php.net/distributions/manual/php_manual_en.html.gz', '/tmp/php_manual_en.html.gz'); diff --git a/examples/custom.php b/examples/custom.php index 3af96f3e8d..1b3c9782a1 100644 --- a/examples/custom.php +++ b/examples/custom.php @@ -1,4 +1,5 @@ exec(); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/delete.php b/examples/delete.php index af20e131d7..6852553d0f 100644 --- a/examples/delete.php +++ b/examples/delete.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Data server received via DELETE:' . "\n"; var_dump($curl->response->form); diff --git a/examples/deviant_art_rss.php b/examples/deviant_art_rss.php index 16012d3a49..dcf9df947c 100644 --- a/examples/deviant_art_rss.php +++ b/examples/deviant_art_rss.php @@ -1,10 +1,11 @@ get('http://backend.deviantart.com/rss.xml', [ +$curl->get('https://backend.deviantart.com/rss.xml', [ 'q' => 'boost:popular in:photography/people/fashion', 'type' => 'deviation', ]); diff --git a/examples/diagnose_request.php b/examples/diagnose_request.php index 2ceb977666..15e955ab84 100644 --- a/examples/diagnose_request.php +++ b/examples/diagnose_request.php @@ -1,4 +1,5 @@ response->photos->photo as $photo) { $size = 's'; $ext = 'jpg'; - $url = 'http://farm/' . $photo->farm . '.staticflickr.com/' . $photo->server . '/' . + $url = 'https://farm/' . $photo->farm . '.staticflickr.com/' . $photo->server . '/' . $photo->id . '_' . $photo->secret . '_' . $size . '.' . $ext; echo ''; } diff --git a/examples/flickr_upload_photo.php b/examples/flickr_upload_photo.php index b32baecc53..aaa0cda6b2 100644 --- a/examples/flickr_upload_photo.php +++ b/examples/flickr_upload_photo.php @@ -2,7 +2,6 @@ require __DIR__ . '/../vendor/autoload.php'; require 'flickr.class.php'; -use Curl\Curl; use Flickr\Flickr; $flickr = new Flickr(); @@ -24,7 +23,7 @@ } else { $user_id = $_SESSION['user_id']; $photo_id = $result->response->photoid; - $photo_url = 'http://www.flickr.com/photos/' . $user_id . '/' . $photo_id; + $photo_url = 'https://www.flickr.com/photos/' . $user_id . '/' . $photo_id; echo '

Photo uploaded successfully. View photo.

'; } } diff --git a/examples/get.php b/examples/get.php index 9de37d780a..78ff21b3d6 100644 --- a/examples/get.php +++ b/examples/get.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/get_base_url_1.php b/examples/get_base_url_1.php index 5f3bcc7391..601c958224 100644 --- a/examples/get_base_url_1.php +++ b/examples/get_base_url_1.php @@ -1,4 +1,5 @@ get('https://code.jquery.com/jquery-1.11.2.min.js'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { var_dump($curl->responseHeaders['status-line']); // HTTP/1.1 206 Partial Content var_dump($curl->responseHeaders['content-length']); // 50 diff --git a/examples/get_pages.php b/examples/get_pages.php index faf4ade4de..c398d017ef 100644 --- a/examples/get_pages.php +++ b/examples/get_pages.php @@ -1,4 +1,5 @@ get('https://www.php.net/'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response cookies:' . "\n"; var_dump($curl->responseCookies); diff --git a/examples/get_with_callable_retry.php b/examples/get_with_callable_retry.php index 1d587de3d0..ea0b61ce44 100644 --- a/examples/get_with_callable_retry.php +++ b/examples/get_with_callable_retry.php @@ -1,4 +1,5 @@ get('https://httpbin.org/status/503'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; echo 'attempts: ' . $curl->attempts . "\n"; echo 'retries: ' . $curl->retries . "\n"; } else { diff --git a/examples/get_with_callable_retry_based_on_http_status_code.php b/examples/get_with_callable_retry_based_on_http_status_code.php index bec254516b..82baba6a5b 100644 --- a/examples/get_with_callable_retry_based_on_http_status_code.php +++ b/examples/get_with_callable_retry_based_on_http_status_code.php @@ -1,4 +1,5 @@ get('https://httpbin.org/status/503'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; echo 'attempts: ' . $curl->attempts . "\n"; echo 'retries: ' . $curl->retries . "\n"; } else { diff --git a/examples/get_with_port.php b/examples/get_with_port.php index a7a73f6476..789636f705 100644 --- a/examples/get_with_port.php +++ b/examples/get_with_port.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/get_with_retry.php b/examples/get_with_retry.php index 737d30550a..c060ffb596 100644 --- a/examples/get_with_retry.php +++ b/examples/get_with_retry.php @@ -1,4 +1,5 @@ get('https://httpbin.org/status/503'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; echo 'attempts: ' . $curl->attempts . "\n"; echo 'retries: ' . $curl->retries . "\n"; } else { diff --git a/examples/get_without_downloading_full_error_response.php b/examples/get_without_downloading_full_error_response.php index abeeea7868..21d25891f2 100644 --- a/examples/get_without_downloading_full_error_response.php +++ b/examples/get_without_downloading_full_error_response.php @@ -1,4 +1,5 @@ get('https://www.example.com/large-500-error'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; echo 'Response content-length: ' . $curl->responseHeaders['content-length'] . "\n"; echo 'Actual response size downloaded: ' . $curl->getInfo(CURLINFO_SIZE_DOWNLOAD) . "\n"; } else { diff --git a/examples/github_create_gist.php b/examples/github_create_gist.php index 8620bc1707..21cfba2a5a 100644 --- a/examples/github_create_gist.php +++ b/examples/github_create_gist.php @@ -1,4 +1,5 @@ put($url, $data); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; var_dump($curl); } else { var_dump($curl->response); diff --git a/examples/gratipay_send_tip.php b/examples/gratipay_send_tip.php index 78eb9234b2..160d265fc0 100644 --- a/examples/gratipay_send_tip.php +++ b/examples/gratipay_send_tip.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response headers:' . "\n"; var_dump($curl->responseHeaders); diff --git a/examples/instagram_popular_media.php b/examples/instagram_popular_media.php index ecbc4c21a3..bb61ae3a9d 100644 --- a/examples/instagram_popular_media.php +++ b/examples/instagram_popular_media.php @@ -1,4 +1,5 @@ setOpt(CURLOPT_POST, true); -$curl_1->setOpt(CURLOPT_POSTFIELDS, [ +$curl_1->setPost('https://httpbin.org/post', [ 'to' => 'alice', 'subject' => 'hi', 'body' => 'hi Alice', ]); -$curl_1->setUrl('https://httpbin.org/post'); $multi_curl->addCurl(http://github.com/$curl_1); $curl_2 = new Curl(); -$curl_2->setOpt(CURLOPT_POST, true); -$curl_2->setOpt(CURLOPT_POSTFIELDS, [ +$curl_2->setPost('https://httpbin.org/post', [ 'to' => 'bob', 'subject' => 'hi', 'body' => 'hi Bob', ]); -$curl_2->setUrl('https://httpbin.org/post'); $multi_curl->addCurl(http://github.com/$curl_2); $multi_curl->start(); diff --git a/examples/multi_curl_add_curl_from_url_list.php b/examples/multi_curl_add_curl_from_url_list.php new file mode 100644 index 0000000000..a148fe7bd2 --- /dev/null +++ b/examples/multi_curl_add_curl_from_url_list.php @@ -0,0 +1,42 @@ +setConcurrency($concurrency); +$multi_curl->complete(function ($instance) use (&$multi_curl, &$urls) { + echo 'complete:' . $instance->url . "\n"; + + // Queue another request each time a request completes. Fetch the oldest url + // next using array_shift($urls) or use the most recently added url using + // array_pop($urls). + // $next_url = array_shift($urls); + // $next_url = array_pop($urls); + $next_url = array_shift($urls); + + if ($next_url !== null) { + $multi_curl->addGet($next_url); + } +}); + +// Queue a few requests. +for ($i = 0; $i < $concurrency; $i++) { + $next_url = array_shift($urls); + if ($next_url !== null) { + $multi_curl->addGet($next_url); + } +} + +$multi_curl->start(); diff --git a/examples/multi_curl_add_curl_low_level.php b/examples/multi_curl_add_curl_low_level.php new file mode 100644 index 0000000000..f932205a39 --- /dev/null +++ b/examples/multi_curl_add_curl_low_level.php @@ -0,0 +1,33 @@ +complete(function ($instance) { + echo 'call to "' . $instance->url . '" completed.' . "\n"; +}); + +$curl_1 = new Curl(); +$curl_1->setOpt(CURLOPT_POST, true); +$curl_1->setOpt(CURLOPT_POSTFIELDS, [ + 'to' => 'alice', + 'subject' => 'hi', + 'body' => 'hi Alice', +]); +$curl_1->setUrl('https://httpbin.org/post'); +$multi_curl->addCurl(http://github.com/$curl_1); + +$curl_2 = new Curl(); +$curl_2->setOpt(CURLOPT_POST, true); +$curl_2->setOpt(CURLOPT_POSTFIELDS, [ + 'to' => 'bob', + 'subject' => 'hi', + 'body' => 'hi Bob', +]); +$curl_2->setUrl('https://httpbin.org/post'); +$multi_curl->addCurl(http://github.com/$curl_2); + +$multi_curl->start(); diff --git a/examples/multi_curl_after_send.php b/examples/multi_curl_after_send.php new file mode 100644 index 0000000000..dc37c67562 --- /dev/null +++ b/examples/multi_curl_after_send.php @@ -0,0 +1,44 @@ +setRetry($max_retries); + +$multi_curl->beforeSend(function ($instance) { + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$multi_curl->error(function ($instance) { + echo 'not lucky this round' . "\n"; +}); + +$multi_curl->success(function ($instance) { + echo + 'success!' . "\n" . + 'got number ' . $instance->response->args->number . ' ' . + 'after ' . $instance->attempts . ' attempt(s).' . "\n"; +}); + +$multi_curl->afterSend(function ($instance) { + $random_number = (int)$instance->response->args->number; + $lucky = $random_number === 7; + $instance->error = !$lucky; + + if (!$lucky) { + $instance->setUrl('https://httpbin.org/get?number=' . random_int(0, 10)); + } +}); + +$multi_curl->addGet('https://httpbin.org/get?number=' . random_int(0, 10)); +$multi_curl->start(); + +// $ php multi_curl_after_send.php +// about to make request to https://httpbin.org/get?number=4 +// about to make request to https://httpbin.org/get?number=7 +// success! +// got number 7 after 2 attempt(s). diff --git a/examples/multi_curl_before_send.php b/examples/multi_curl_before_send.php index dd03d615e7..f0a03beb73 100644 --- a/examples/multi_curl_before_send.php +++ b/examples/multi_curl_before_send.php @@ -1,4 +1,5 @@ setRetry($max_retries); + +$multi_curl->beforeSend(function ($instance) { + echo 'current attempts: ' . $instance->attempts . "\n"; + echo 'current retries: ' . $instance->retries . "\n"; + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$multi_curl->complete(function ($instance) { + if ($instance->error) { + echo 'Error: ' . $instance->errorMessage . "\n"; + echo 'final attempts: ' . $instance->attempts . "\n"; + echo 'final retries: ' . $instance->retries . "\n"; + } else { + echo 'Response:' . "\n"; + var_dump($instance->response); + } +}); + +$multi_curl->addGet('https://httpbin.org/status/503'); + +$multi_curl->start(); diff --git a/examples/multi_curl_delete.php b/examples/multi_curl_delete.php index 46d00a8c20..b9f8670dc3 100644 --- a/examples/multi_curl_delete.php +++ b/examples/multi_curl_delete.php @@ -1,4 +1,5 @@ addGet($url); +} +$multi_curl->complete(function ($instance) use (&$responses) { + // Store responses. + $responses[] = $instance->response; + + // Alternatively, process each response here inside the callback as it is received. +}); +$multi_curl->start(); + +// Process responses. +foreach ($responses as $response) { + var_dump($response); +} diff --git a/examples/multi_curl_get_relative.php b/examples/multi_curl_get_relative.php index bb9bcacc37..ee83d9e49b 100644 --- a/examples/multi_curl_get_relative.php +++ b/examples/multi_curl_get_relative.php @@ -1,4 +1,5 @@ complete(function ($instance) { echo - 'curl id ' . $instance->id . ':' . "\n" . + 'curl id ' . $instance->id . ' completed:' . "\n" . '- ip: ' . $instance->response->origin . "\n" . '- proxy: ' . $instance->getOpt(CURLOPT_PROXY) . "\n" . '- url: ' . $instance->effectiveUrl . '' . "\n" . diff --git a/examples/multi_curl_get_with_new_random_unique_proxy.php b/examples/multi_curl_get_with_new_random_unique_proxy.php new file mode 100644 index 0000000000..05405cecbc --- /dev/null +++ b/examples/multi_curl_get_with_new_random_unique_proxy.php @@ -0,0 +1,50 @@ +setProxyType(CURLPROXY_SOCKS5); +$multi_curl->beforeSend(function ($instance) use ($proxies, &$current_proxies) { + if (!count($current_proxies)) { + $current_proxies = $proxies; + } + + // Use random proxy that hasn't been used yet. + $rand_key = ArrayUtil::arrayRandomIndex($current_proxies); + $new_random_proxy = $current_proxies[$rand_key]; + $instance->setProxy($new_random_proxy); + + // Remove proxy from list of current proxies. + unset($current_proxies[$rand_key]); + + // Re-index list of current proxies. + $current_proxies = array_values($current_proxies); + + echo 'about to make request ' . $instance->id . ' using proxy "' . $new_random_proxy . '".' . "\n"; +}); + +$multi_curl->addGet('https://httpbin.org/ip'); +$multi_curl->addGet('https://httpbin.org/ip'); +$multi_curl->addGet('https://httpbin.org/ip'); +$multi_curl->addGet('https://httpbin.org/ip'); + +$multi_curl->complete(function ($instance) { + echo + 'curl id ' . $instance->id . ' completed:' . "\n" . + '- ip: ' . $instance->response->origin . "\n" . + '- proxy: ' . $instance->getOpt(CURLOPT_PROXY) . "\n" . + '- url: ' . $instance->effectiveUrl . '' . "\n" . + ''; +}); + +$multi_curl->start(); diff --git a/examples/multi_curl_get_with_rate_limit.php b/examples/multi_curl_get_with_rate_limit.php index 6c52ce5bba..612182bcbd 100644 --- a/examples/multi_curl_get_with_rate_limit.php +++ b/examples/multi_curl_get_with_rate_limit.php @@ -1,4 +1,5 @@ ] +// 23% [========> ] +// 15% [=====> ] +// +// Note: The server needs to send a content-length header for progress updates to work. + +use Curl\MultiCurl; + +// Keep track of download progress for each of the downloads. +$download_status = []; + +// Keep track of when the screen was last updated so it not updated too frequently. +$last_updated_time = 0; + +$multi_curl = new MultiCurl(); + +$urls_to_download = [ + 'https://www.php.net/distributions/manual/php_manual_en.html.gz', + 'https://www.php.net/distributions/manual/php_manual_en.tar.gz', + 'https://www.php.net/distributions/manual/php_manual_en.chm', +]; + +$i = 0; +foreach ($urls_to_download as $url) { + $filename = basename($url); + echo 'will be downloading ' . $url . ' and saving as "' . $filename . '"' . "\n"; + + $download_status[$i] = [ + 'position' => $i, + 'complete' => false, + 'filename' => $filename, + 'size' => 0, + 'downloaded' => 0, + ]; + + $curl = $multi_curl->addDownload($url, $filename); + + // Increase timeout to avoid error: + // "Operation timed out after 30000 milliseconds with ... out of ... bytes + // received". + $curl->setTimeout(500); + + // Slow the download. Comment the following lines to remove the download + // throttling. + $curl->setOpt(CURLOPT_MAX_RECV_SPEED_LARGE, 500000); + $curl->setOpt(CURLOPT_BUFFERSIZE, 1024); + + $curl->progress(function ( + $client, + $download_size, + $downloaded, + $upload_size, + $uploaded + ) use ( + $i, + &$download_status, + &$last_updated_time + ) { + if ($download_size === 0) { + return 0; + } + + $download_completed = $downloaded === $download_size; + $current_time = time(); + + // Avoid sending an update if we're within the same second and the + // download has not yet completed. + if (!$download_completed && $current_time === $last_updated_time) { + return 0; + } + + $last_updated_time = $current_time; + + // Update progress of this download. + $download_status[$i]['complete'] = $download_completed; + $download_status[$i]['size'] = $download_size; + $download_status[$i]['downloaded'] = $downloaded; + + // Generate response including completion status of all downloads and + // status of each individual download. + $response = [ + 'status' => '', + 'downloads' => [], + ]; + $all_downloads_completed = true; + foreach ($download_status as $key => $value) { + $response['downloads'][] = $value; + $all_downloads_completed = $all_downloads_completed && $value['complete']; + } + $response['status'] = $all_downloads_completed ? 'done' : 'active'; + $json_response = json_encode($response); + + $out = fopen('/tmp/myfifo', 'w'); + + // TODO: Catch broken pipe: + // PHP Notice: fwrite(): Write of 52 bytes failed with errno=32 + // Broken pipe in ./multi_curl_progress_advanced.php on line [...] + fwrite($out, $json_response . "\n"); + + fclose($out); + + // Comment the following line to hide the download progress updates + // being sent to the named pipe. + echo $json_response . "\n"; + + return 0; + }); + + $i += 1; +} + +echo 'starting download' . "\n"; +$multi_curl->start(); + +echo 'all done' . "\n"; diff --git a/examples/multi_curl_progress_advanced_watch_curses.py b/examples/multi_curl_progress_advanced_watch_curses.py new file mode 100644 index 0000000000..0f50424446 --- /dev/null +++ b/examples/multi_curl_progress_advanced_watch_curses.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +# Reads a named pipe file and displays progress bars using curses. +# +# See multi_curl_progress_advanced.php +# +# $ ipython multi_curl_progress_advanced_watch_curses.py +# 56% [=====================> ] +# 23% [========> ] +# 15% [=====> ] + +import curses +import json +import os + +FIFO = "/tmp/myfifo" + + +def main(stdscr): + # Create named pipe file if it doesn't exist. + if not os.path.exists(FIFO): + os.mkfifo(FIFO) + + curses.noecho() + curses.cbreak() + + try: + while True: + display_progress_bars(stdscr) + except KeyboardInterrupt: + # Handle Control-C pressed. + pass + finally: + curses.echo() + curses.nocbreak() + curses.endwin() + + # Return exit code 0. + return 0 + + +def display_progress_bars(stdscr): + display_notice = True + + while True: + if display_notice: + display_notice = False + stdscr.clear() + stdscr.addstr(0, 0, "waiting for input") + stdscr.refresh() + + # Read named pipe file. + with open(FIFO) as f: + for line in f: + response = json.loads(line) + + # Update progress for each of the files being downloaded. + for entry in response.get("downloads", []): + # Display a progress bar: xxx% [=======> ] + progress_size = 40 + try: + fraction_downloaded = entry["downloaded"] / entry["size"] + except ZeroDivisionError: + fraction_downloaded = 0 + dots = round(fraction_downloaded * progress_size) + task_progress = "%3.0f%% [" % (fraction_downloaded * 100) + + i = 0 + while i < dots - 1: + task_progress += "=" + i += 1 + + task_progress += ">" + + while i < progress_size - 1: + task_progress += " " + i += 1 + + task_progress += "]" + + stdscr.addstr(entry["position"], 0, task_progress) + + # Refresh display of the progress bars. + stdscr.refresh() + + # Exit only after progress bars have been updated to end with + # each displaying 100%. + if response.get("status", "") == "done": + return + + +if __name__ == "__main__": + # Avoid getting the terminal in an unmanagable state by using the curses + # wrapper. The wrapper restores the terminal to its previous state even when + # there is an uncaught exception. + curses.wrapper(main) diff --git a/examples/multi_curl_progress_advanced_watch_tqdm.py b/examples/multi_curl_progress_advanced_watch_tqdm.py new file mode 100644 index 0000000000..f82158997c --- /dev/null +++ b/examples/multi_curl_progress_advanced_watch_tqdm.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +# Reads a named pipe file and displays progress bars using tqdm. +# +# See multi_curl_progress_advanced.php +# +# $ ipython multi_curl_progress_advanced_watch_tqdm.py +# php_manual_en.html.gz: 56%|████████████████████████ | 2.99M/5.34M [00:02<00:02, 1.14MB/s] +# php_manual_en.tar.gz: 23%|██████████▎ | 2.37M/10.4M [00:02<00:08, 901kB/s] +# php_manual_en.chm: 15%|███████▏ | 2.06M/13.7M [00:02<00:14, 783kB/s] + +import json +import os +import sys + +from tqdm import tqdm + +FIFO = "/tmp/myfifo" + + +def main(): + # Create named pipe file if it doesn't exist. + if not os.path.exists(FIFO): + os.mkfifo(FIFO) + + try: + while True: + display_progress_bars() + except KeyboardInterrupt: + # Handle Control-C pressed. + pass + + # Return exit code 0. + return 0 + + +def display_progress_bars(): + display_notice = True + progress_bars = {} + + while True: + if display_notice: + display_notice = False + print("waiting for input") + + # Read named pipe file. + with open(FIFO) as f: + for line in f: + response = json.loads(line) + + # Update progress for each of the files being downloaded. + for entry in response.get("downloads", []): + # Get or create progress bar. + progress_bar = progress_bars.get(entry["position"]) + if progress_bar is None: + progress_bar = tqdm( + desc=entry["filename"], + total=entry["size"], + position=entry["position"], + unit_scale=True, + unit_divisor=1000, + unit="B", + ) + progress_bars[entry["position"]] = progress_bar + + # Update download progress. + progress_bar.n = entry["downloaded"] + + # Update progress bar's total size. Initial size may have + # been sent as 0 until content-length was determined. + progress_bar.total = entry["size"] + + # Refresh display of the progress bar. + progress_bar.refresh() + + # Exit only after progress bars have been updated to end with + # each displaying 100%. + if response.get("status", "") == "done": + return + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/multi_curl_proxies.php b/examples/multi_curl_proxies.php index 3bb00eb5c0..1474c92c93 100644 --- a/examples/multi_curl_proxies.php +++ b/examples/multi_curl_proxies.php @@ -1,4 +1,5 @@ 'https://httpbin.org/post', - 'tag4' => 'https://httpbin.org/get', - 'tag5' => 'https://httpbin.org/html', +$tags_to_urls = [ + 'tag3' => 'https://httpbin.org/status/401', + 'tag4' => 'https://httpbin.org/status/200', + 'tag5' => 'https://httpbin.org/status/503', ]; +$ids_to_tags = []; + $multi_curl = new MultiCurl(); -$multi_curl->success(function ($instance) { - echo 'call to ' . $instance->id . ' with "' . $instance->myTag . '" was successful.' . "\n"; +$multi_curl->success(function ($instance) use (&$ids_to_tags) { + echo + 'instance id ' . $instance->id . ' request with tag ' . + $ids_to_tags[$instance->id] . ' was successful.' . "\n"; }); -$multi_curl->error(function ($instance) { - echo 'call to ' . $instance->id . ' with "' . $instance->myTag . '" was unsuccessful.' . "\n"; +$multi_curl->error(function ($instance) use (&$ids_to_tags) { + echo + 'instance id ' . $instance->id . ' request with tag ' . + $ids_to_tags[$instance->id] . ' was unsuccessful.' . "\n"; }); -$multi_curl->complete(function ($instance) { - echo 'call to ' . $instance->id . ' with "' . $instance->myTag . '" completed.' . "\n"; +$multi_curl->complete(function ($instance) use (&$ids_to_tags) { + echo + 'instance id ' . $instance->id . ' request with tag ' . + $ids_to_tags[$instance->id] . ' completed.' . "\n"; }); -foreach ($urls as $tag => $url) { - $instance = $multi_curl->addGet($url); - $instance->myTag = $tag; +foreach ($tags_to_urls as $tag => $url) { + $curl = $multi_curl->addGet($url, ['myTag' => $tag]); + $ids_to_tags[$curl->id] = $tag; } $multi_curl->start(); + +/* +$ php multi_curl_set_custom_instance_tag.php +instance id 0 request with tag tag3 was unsuccessful. +instance id 0 request with tag tag3 completed. +instance id 2 request with tag tag5 was unsuccessful. +instance id 2 request with tag tag5 completed. +instance id 1 request with tag tag4 was unsuccessful. +instance id 1 request with tag tag4 completed. +*/ diff --git a/examples/multi_curl_stop.php b/examples/multi_curl_stop.php new file mode 100644 index 0000000000..ac6e4e526e --- /dev/null +++ b/examples/multi_curl_stop.php @@ -0,0 +1,39 @@ +beforeSend(function ($instance) { + echo 'about to make request ' . $instance->id . ': "' . $instance->url . '".' . "\n"; +}); + +$multi_curl->success(function ($instance) use ($multi_curl) { + echo 'call to "' . $instance->url . '" was successful.' . "\n"; + + // Stop pending requests and attempt to stop active requests after the first + // successful request. + $multi_curl->stop(); +}); + +$multi_curl->error(function ($instance) { + echo 'call to "' . $instance->url . '" was unsuccessful.' . "\n"; +}); + +// Count the number of completed requests. +$request_count = 0; +$multi_curl->complete(function ($instance) use (&$request_count) { + echo 'call to "' . $instance->url . '" completed.' . "\n"; + $request_count += 1; +}); + +$multi_curl->addGet('https://httpbin.org/delay/4'); +$multi_curl->addGet('https://httpbin.org/delay/1'); +$multi_curl->addGet('https://httpbin.org/delay/3'); +$multi_curl->addGet('https://httpbin.org/delay/2'); + +$multi_curl->start(); + +assert($request_count === 1); diff --git a/examples/multi_curl_track_success_urls.php b/examples/multi_curl_track_success_urls.php new file mode 100644 index 0000000000..7060c4330d --- /dev/null +++ b/examples/multi_curl_track_success_urls.php @@ -0,0 +1,126 @@ + [], + 'successful' => [], + 'errors' => [], + 'completed' => [], + 'not_completed' => [], +]; + +$multi_curl = new MultiCurl(); +$multi_curl->setConcurrency(2); + +foreach ($urls as $url) { + // Queue requests. + $request = $multi_curl->addGet($url); + + // Track all requests queued. + $request_stats['all'][] = $request->url; +} + +// Track successful requests. +$multi_curl->success(function ($instance) use (&$request_stats, $multi_curl) { + $request_stats['successful'][] = $instance->url; + + // Optionally, stop additional requests based on some condition (e.g. stop + // after a number of successful requests). + if (count($request_stats['successful']) >= 3) { + $multi_curl->stop(); + } +}); + +// Track requests that errored. +$multi_curl->error(function ($instance) use (&$request_stats) { + $request_stats['errors'][] = $instance->url; +}); + +// Track requests that completed. +$multi_curl->complete(function ($instance) use (&$request_stats) { + $request_stats['completed'][] = $instance->url; +}); + +$multi_curl->start(); + +// Determine urls not completed. +$request_stats['not_completed'] = array_diff($request_stats['all'], $request_stats['completed']); + +// Display results. +var_dump($request_stats); + +// $ php multi_curl_track_success_urls.php +// array(5) { +// ["all"]=> +// array(8) { +// [0]=> +// string(30) "https://httpbin.org/status/503" +// [1]=> +// string(30) "https://httpbin.org/status/200" +// [2]=> +// string(30) "https://httpbin.org/status/401" +// [3]=> +// string(27) "https://httpbin.org/delay/3" +// [4]=> +// string(30) "https://httpbin.org/status/201" +// [5]=> +// string(27) "https://httpbin.org/delay/1" +// [6]=> +// string(30) "https://httpbin.org/status/500" +// [7]=> +// string(30) "https://httpbin.org/status/504" +// } +// ["successful"]=> +// array(3) { +// [0]=> +// string(30) "https://httpbin.org/status/200" +// [1]=> +// string(27) "https://httpbin.org/delay/3" +// [2]=> +// string(30) "https://httpbin.org/status/201" +// } +// ["errors"]=> +// array(2) { +// [0]=> +// string(30) "https://httpbin.org/status/503" +// [1]=> +// string(30) "https://httpbin.org/status/401" +// } +// ["completed"]=> +// array(5) { +// [0]=> +// string(30) "https://httpbin.org/status/200" +// [1]=> +// string(30) "https://httpbin.org/status/503" +// [2]=> +// string(30) "https://httpbin.org/status/401" +// [3]=> +// string(27) "https://httpbin.org/delay/3" +// [4]=> +// string(30) "https://httpbin.org/status/201" +// } +// ["not_completed"]=> +// array(3) { +// [5]=> +// string(27) "https://httpbin.org/delay/1" +// [6]=> +// string(30) "https://httpbin.org/status/500" +// [7]=> +// string(30) "https://httpbin.org/status/504" +// } +// } diff --git a/examples/multi_curl_upload_file.php b/examples/multi_curl_upload_file.php index b4aa70d4d7..568cdb0ef2 100644 --- a/examples/multi_curl_upload_file.php +++ b/examples/multi_curl_upload_file.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/patch.php b/examples/patch.php index a3ce50f1a6..f4c86d1474 100644 --- a/examples/patch.php +++ b/examples/patch.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/post.php b/examples/post.php index 58024145ef..5545b73ee5 100644 --- a/examples/post.php +++ b/examples/post.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Data server received via POST:' . "\n"; var_dump($curl->response->form); diff --git a/examples/post_json.php b/examples/post_json.php index 37bab8c587..a0533c9dce 100644 --- a/examples/post_json.php +++ b/examples/post_json.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Data server received via PUT:' . "\n"; var_dump($curl->response->form); diff --git a/examples/put_large_file_chunked.php b/examples/put_large_file_chunked.php index 6368e904fe..020b81877f 100644 --- a/examples/put_large_file_chunked.php +++ b/examples/put_large_file_chunked.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/set_cookie.php b/examples/set_cookie.php index 1a54257937..c3a09ef571 100644 --- a/examples/set_cookie.php +++ b/examples/set_cookie.php @@ -1,4 +1,5 @@ error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Success' . "\n"; } diff --git a/examples/use_custom_xml_decoder.php b/examples/use_custom_xml_decoder.php index bf5ec0eb09..d388eb24a5 100644 --- a/examples/use_custom_xml_decoder.php +++ b/examples/use_custom_xml_decoder.php @@ -1,4 +1,5 @@ get('https://httpbin.org/xml'); if ($curl->error) { - echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; + echo 'Error: ' . $curl->errorMessage . "\n"; } else { echo 'Response:' . "\n"; var_dump($curl->response); diff --git a/examples/youtube_list_playlist_videos.php b/examples/youtube_list_playlist_videos.php index bb5337f84d..a98320a723 100644 --- a/examples/youtube_list_playlist_videos.php +++ b/examples/youtube_list_playlist_videos.php @@ -1,4 +1,5 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/bump_major_version.php b/scripts/bump_major_version.php index 798f9bd64f..77b03069a4 100755 --- a/scripts/bump_major_version.php +++ b/scripts/bump_major_version.php @@ -1,22 +1,31 @@ -#!/usr/bin/php +#!/usr/bin/env php $message, + 'old_version' => $current_version, + 'new_version' => $new_version, +], JSON_PRETTY_PRINT) . "\n"; diff --git a/scripts/bump_minor_version.php b/scripts/bump_minor_version.php index f61751e91e..0de25185e0 100755 --- a/scripts/bump_minor_version.php +++ b/scripts/bump_minor_version.php @@ -1,22 +1,31 @@ -#!/usr/bin/php +#!/usr/bin/env php $message, + 'old_version' => $current_version, + 'new_version' => $new_version, +], JSON_PRETTY_PRINT) . "\n"; diff --git a/scripts/bump_patch_version.php b/scripts/bump_patch_version.php index e4dee9b155..0b6804ae3a 100755 --- a/scripts/bump_patch_version.php +++ b/scripts/bump_patch_version.php @@ -1,22 +1,31 @@ -#!/usr/bin/php +#!/usr/bin/env php $message, + 'old_version' => $current_version, + 'new_version' => $new_version, +], JSON_PRETTY_PRINT) . "\n"; diff --git a/scripts/make_release.py b/scripts/make_release.py new file mode 100644 index 0000000000..666b483007 --- /dev/null +++ b/scripts/make_release.py @@ -0,0 +1,255 @@ +import json +import os +import pprint +import subprocess +from copy import copy +from datetime import datetime +from datetime import timezone +from pathlib import Path + +from git.repo import Repo +from github import Github + +# The owner and repository name. For example, octocat/Hello-World. +GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY", "") + +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") +GITHUB_REF_NAME = os.getenv("GITHUB_REF_NAME") + +CURRENT_FILE = Path(__file__) +ROOT = CURRENT_FILE.parents[1] +CHANGELOG_PATH = ROOT / "CHANGELOG.md" +LIBRARY_FILE_PATH = ROOT / "src/Curl/Curl.php" + +# TODO: Adjust number of recent pull requests to include likely number of +# pull requests since the last release. +RECENT_PULL_REQUEST_LIMIT = 50 + + +def main(): + # Find most recent tag and timestamp. + # git for-each-ref --format="%(refname:short) | %(creatordate)" "refs/tags/*" + local_repo = Repo(ROOT) + + # Sort the tags by version. + # git tag --list | sort --reverse --version-sort + tags = sorted( + local_repo.tags, + key=lambda tag: list(map(int, tag.name.split("."))), + reverse=True, + ) + + most_recent_tag = tags[0] + print("most_recent_tag: {}".format(most_recent_tag)) + most_recent_tag_datetime = most_recent_tag.commit.committed_datetime + print("most_recent_tag_datetime: {}".format(most_recent_tag_datetime)) + + g = Github(login_or_token=GITHUB_TOKEN) + + # Find merged pull requests since the most recent tag. + github_repo = g.get_repo(GITHUB_REPOSITORY) + recent_pulls = github_repo.get_pulls( + state="closed", + sort="updated", + direction="desc", + )[:RECENT_PULL_REQUEST_LIMIT] + + pull_request_changes = [] + + # Group pull requests by semantic version change type. + pull_request_by_type = { + "major": [], + "minor": [], + "patch": [], + "cleanup": [], + "unspecified": [], + } + + # Track if any pull request is missing a semantic version change type. + pulls_missing_semver_label = [] + + for pull in recent_pulls: + # print('-' * 10) + + if not pull.merged: + # print('skipping since not merged: {}'.format(pull.title)) + # print(pull.html_url) + continue + + # Make merged_at timestamp offset-aware. Without this, the following + # error will appear: + # TypeError: can't compare offset-naive and offset-aware datetimes + pull_merged_at = copy(pull.merged_at).replace(tzinfo=timezone.utc) + + if pull_merged_at < most_recent_tag_datetime: + # print('skipping since merged prior to last release: {}'.format(pull.title)) + # print(pull.html_url) + continue + + pull_labels = {label.name for label in pull.labels} + if "major-incompatible-changes" in pull_labels: + group_name = "major" + elif "minor-backwards-compatible-added-functionality" in pull_labels: + group_name = "minor" + elif "patch-backwards-compatible-bug-fixes" in pull_labels: + group_name = "patch" + elif "cleanup-no-release-required" in pull_labels: + group_name = "cleanup" + else: + group_name = "unspecified" + pulls_missing_semver_label.append(pull) + pull_request_by_type[group_name].append(pull) + + # pprint.pprint(pull.title) + # pprint.pprint('most recent: {}'.format(most_recent_tag_datetime)) + # pprint.pprint('merged at: {}'.format(pull.merged_at)) + # print(pull.html_url) + + if group_name in ["major", "minor", "patch"]: + pull_request_changes.append( + "- {} ([#{}]({}))".format(pull.title, pull.number, pull.html_url) + ) + + # print('-' * 10) + + # pprint.pprint(pull_request_changes) + + # Raise error if any pull request is missing a semantic version change type. + # Do this check before checking for any pull request changes as all the pull + # requests changes might be missing semver labels. + if pulls_missing_semver_label: + error_message = ( + "Merged pull request(s) found without semantic version label:\n" + "{}".format( + "\n".join( + " {}".format(pull.html_url) for pull in pulls_missing_semver_label + ) + ) + ) + raise Exception(error_message) + + if not pull_request_changes: + print("No merged pull requests since the most recent tag release were found") + return + + # pprint.pprint(pull_request_by_type) + if pull_request_by_type.get("major"): + highest_semantic_version = "major" + php_file_path = "scripts/bump_major_version.php" + elif pull_request_by_type.get("minor"): + highest_semantic_version = "minor" + php_file_path = "scripts/bump_minor_version.php" + elif pull_request_by_type.get("patch"): + highest_semantic_version = "patch" + php_file_path = "scripts/bump_patch_version.php" + else: + highest_semantic_version = None + php_file_path = "" + print("highest_semantic_version: {}".format(highest_semantic_version)) + + # Bump version and get next semantic version. + command = ["php", php_file_path] + print("running command: {}".format(command)) + proc = subprocess.Popen( + command, shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE + ) + stdout, stderr = proc.communicate() + print("stdout: {}".format(stdout)) + print("stderr: {}".format(stderr)) + result = json.loads(stdout) + pprint.pprint(result) + + release_version = result["new_version"] + today = datetime.today() + print("today: {} (tzinfo={})".format(today, today.tzinfo)) + today = today.replace(tzinfo=timezone.utc) + print("today: {} (tzinfo={})".format(today, today.tzinfo)) + release_date = today.strftime("%Y-%m-%d") + print("release_date: {}".format(release_date)) + release_title = "{} - {}".format(release_version, release_date) + print("release_title: {}".format(release_title)) + + release_content = "".join( + [ + "## {}\n", + "\n", + "{}", + ] + ).format(release_title, "\n".join(pull_request_changes)) + + old_content = CHANGELOG_PATH.read_text() + new_content = old_content.replace( + "", + "\n\n{}".format(release_content), + ) + # print(new_content[:800]) + CHANGELOG_PATH.write_text(new_content) + + # print('git status before adding files:') + # print(local_repo.git.status()) + + local_repo.git.add(CHANGELOG_PATH) + local_repo.git.add(LIBRARY_FILE_PATH) + + # print('git status after adding files:') + # print(local_repo.git.status()) + + # print('diff:') + # git diff --cached --color=always + # print(local_repo.git.diff(cached=True, color='always')) + + local_repo.git.commit( + message=result["message"], + author="{} <{}>".format( + local_repo.git.config("--get", "user.name"), + local_repo.git.config("--get", "user.email"), + ), + ) + + print("diff after commit:") + # git log --max-count=1 --patch --color=always + print(local_repo.git.log(max_count="1", patch=True, color="always")) + + # Push local changes. + server = "https://%7B%7D@github.com/%7B%7D.git".format(GITHUB_TOKEN, GITHUB_REPOSITORY) + print( + 'pushing changes to branch "{}" of repository "{}"'.format( + GITHUB_REF_NAME, GITHUB_REPOSITORY + ) + ) + local_repo.git.push(server, GITHUB_REF_NAME) + + # Create tag and release. + tag = result["new_version"] + tag_message = result["message"] + release_name = "Release {}".format(release_version) + release_message = ( + "See [change log]" + "(https://github.com/php-curl-class/php-curl-class/blob/master/CHANGELOG.md) for changes.\n" + "\n" + "https://github.com/php-curl-class/php-curl-class/compare/%7B%7D...%7B%7D".format( + result["old_version"], + result["new_version"], + ) + ) + commit_sha = local_repo.head.commit.hexsha + print("tag: {}".format(tag)) + print('tag_message: "{}"'.format(tag_message)) + print('release_name: "{}"'.format(release_name)) + print('release_message: "{}"'.format(release_message)) + print("commit_sha: {}".format(commit_sha)) + + github_repo.create_git_tag_and_release( + tag=tag, + tag_message=tag_message, + release_name=release_name, + release_message=release_message, + object=commit_sha, + type="commit", + draft=False, + ) + print("created tag and release") + + +if __name__ == "__main__": + main() diff --git a/scripts/make_release_recreate.sh b/scripts/make_release_recreate.sh new file mode 100755 index 0000000000..ae3dc14d9e --- /dev/null +++ b/scripts/make_release_recreate.sh @@ -0,0 +1,8 @@ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +rm -v "make_release_requirements.txt" + +pip-compile \ + --output-file="make_release_requirements.txt" \ + "make_release_requirements.in" diff --git a/scripts/make_release_requirements.in b/scripts/make_release_requirements.in new file mode 100644 index 0000000000..b444b66661 --- /dev/null +++ b/scripts/make_release_requirements.in @@ -0,0 +1,2 @@ +gitpython +pygithub diff --git a/scripts/make_release_requirements.json b/scripts/make_release_requirements.json new file mode 100644 index 0000000000..8da701d1de --- /dev/null +++ b/scripts/make_release_requirements.json @@ -0,0 +1,5 @@ +[ + { + "outdated_count": "0" + } +] diff --git a/scripts/make_release_requirements.txt b/scripts/make_release_requirements.txt new file mode 100644 index 0000000000..92c3466917 --- /dev/null +++ b/scripts/make_release_requirements.txt @@ -0,0 +1,40 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --output-file=scripts/make_release_requirements.txt scripts/make_release_requirements.in +# +certifi==2025.11.12 + # via requests +cffi==2.0.0 + # via + # cryptography + # pynacl +charset-normalizer==3.4.4 + # via requests +cryptography==46.0.3 + # via pyjwt +gitdb==4.0.12 + # via gitpython +gitpython==3.1.45 + # via -r scripts/make_release_requirements.in +idna==3.11 + # via requests +pycparser==2.23 + # via cffi +pygithub==2.8.1 + # via -r scripts/make_release_requirements.in +pyjwt[crypto]==2.10.1 + # via pygithub +pynacl==1.6.1 + # via pygithub +requests==2.32.5 + # via pygithub +smmap==5.0.2 + # via gitdb +typing-extensions==4.15.0 + # via pygithub +urllib3==2.5.0 + # via + # pygithub + # requests diff --git a/scripts/make_release_update_requirements.sh b/scripts/make_release_update_requirements.sh new file mode 100755 index 0000000000..9786d88273 --- /dev/null +++ b/scripts/make_release_update_requirements.sh @@ -0,0 +1,32 @@ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +update_package() { + local package_name="${1}" + local package_version="${2}" + + if [[ ! -z "${package_version}" ]]; then + echo "Updating ${package_name} to version ${package_version}" + local upgrade_package="${package_name}==${package_version}" + else + echo "Updating ${package_name}" + local upgrade_package="${package_name}" + fi + + pip-compile \ + --upgrade-package="${upgrade_package}" \ + --output-file="make_release_requirements.txt" \ + "make_release_requirements.in" +} + +package_name="${1}" +package_version="${2}" + +if [[ -z "${package_name}" ]]; then + echo "No package name provided. Usage: $0 []" + exit 1 +fi + +set -x + +update_package "${package_name}" "${package_version}" diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh new file mode 100755 index 0000000000..5ef8d3b18f --- /dev/null +++ b/scripts/pre-commit.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +list_of_files="${@}" +for file in $list_of_files; do + if [[ "${file}" == "composer.json" ]]; then + $(which composer) validate + exit "${?}" + else + echo "unsupported file: ${file}" + exit 1 + fi +done + +exit 0 diff --git a/scripts/update_readme_methods.sh b/scripts/update_readme_methods.sh index 6f6a77397d..5c8574513e 100755 --- a/scripts/update_readme_methods.sh +++ b/scripts/update_readme_methods.sh @@ -1,20 +1,45 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}/.." before=$(head -n $( - grep --context="0" --line-number --max-count="1" "### Available Methods" "README.md" | + grep --context="0" --line-number --max-count="1" "### 📖 Available Methods" "README.md" | perl -pe 's/^(\d+):.*/\1/') "README.md") after=$(tail -n +$( - grep --context="0" --line-number --max-count="1" "### Security" "README.md" | + grep --context="0" --line-number --max-count="1" "### 🔒 Security" "README.md" | perl -pe 's/^(\d+):.*/\1/') "README.md") echo "${before}" > "README.md" +basecurl_path="src/Curl/BaseCurl.php" +curl_path="src/Curl/Curl.php" +multicurl_path="src/Curl/MultiCurl.php" + echo '```php' >> "README.md" -find "src/Curl" -type f -name "*Curl*" | - sort | - xargs -L 1 -I {} bash -c 'class_name="$(basename --suffix=".php" "{}")" && egrep "^ .* function .*" "{}" | egrep "^ public" | sort | perl -pe "s/^ public (.* )?function /${class_name}::/"' >> "README.md" + +curl_class_name="$(basename --suffix=".php" "${curl_path}")" && +curl_fns="$( + grep --extended-regexp "^ .* function .*" "${curl_path}" | + grep --extended-regexp "^ public" | + perl -pe "s/^ public (.* )?function /${curl_class_name}::/")" + +multicurl_class_name="$(basename --suffix=".php" "${multicurl_path}")" && +multicurl_fns="$( + grep --extended-regexp "^ .* function .*" "${multicurl_path}" | + grep --extended-regexp "^ public" | + perl -pe "s/^ public (.* )?function /${multicurl_class_name}::/")" + +common_fns="$( + grep --extended-regexp "^ .* function .*" "${basecurl_path}" | + grep --extended-regexp "^ public" | + perl -pe "s/^ public .* ?function (.*)/Curl::\1\nMultiCurl::\1/")" + +echo "${curl_fns} +${multicurl_fns} +${common_fns}" | sort | uniq >> "README.md" + echo '```' >> "README.md" echo >> "README.md" @@ -23,10 +48,23 @@ echo "${after}" >> "README.md" # Update table of contents. script=$(cat <<'EOF' $data = file_get_contents('README.md'); - preg_match_all('/^### ([\w ]+)/m', $data, $matches); + preg_match_all('/^### (.*)/m', $data, $matches); $toc = []; foreach ($matches['1'] as $match) { - $href = '#' . str_replace(' ', '-', strtolower($match)); + $slug = urlencode( + strtolower( + str_replace( + ' ', + '-', + preg_replace( + '/[^A-Za-z\x{FE0F} ]/u', + '', + $match, + ), + ) + ) + ); + $href = '#' . $slug; $toc[] = '- [' . $match . '](' . $href . ')'; } $toc = implode("\n", $toc); diff --git a/src/Curl/ArrayUtil.php b/src/Curl/ArrayUtil.php index 00277b23fa..20a83c8fdb 100644 --- a/src/Curl/ArrayUtil.php +++ b/src/Curl/ArrayUtil.php @@ -1,18 +1,16 @@ - $value) { if (is_scalar($value)) { if ($prefix) { - $return[$prefix . '[' . $key . ']'] = $value; + $arrays_to_merge[] = [ + $prefix . '[' . $key . ']' => $value, + ]; } else { - $return[$key] = $value; + $arrays_to_merge[] = [ + $key => $value, + ]; } + } elseif ($value instanceof \CURLFile) { + $arrays_to_merge[] = [ + $key => $value, + ]; + } elseif ($value instanceof \CURLStringFile) { + $arrays_to_merge[] = [ + $key => $value, + ]; } else { - if ($value instanceof \CURLFile) { - $return[$key] = $value; - } else { - $return = array_merge( - $return, - self::arrayFlattenMultidim( - $value, - $prefix ? $prefix . '[' . $key . ']' : $key - ) - ); - } + $arrays_to_merge[] = self::arrayFlattenMultidim( + $value, + $prefix ? $prefix . '[' . $key . ']' : $key + ); } } + + $return = array_merge($return, ...$arrays_to_merge); } } elseif ($array === null) { $return[$prefix] = $array; @@ -115,10 +114,8 @@ public static function arrayFlattenMultidim($array, $prefix = false) * Array Flatten Multidim * * @deprecated Use ArrayUtil::arrayFlattenMultidim(). - * @access public - * @param $array - * @param $prefix - * + * @param $array + * @param $prefix * @return array */ public static function array_flatten_multidim($array, $prefix = false) @@ -129,23 +126,30 @@ public static function array_flatten_multidim($array, $prefix = false) /** * Array Random * - * @access public - * @param $array - * + * @param $array * @return mixed */ public static function arrayRandom($array) { - return $array[mt_rand(0, count($array) - 1)]; + return $array[static::arrayRandomIndex($array)]; + } + + /** + * Array Random Index + * + * @param $array + * @return int + */ + public static function arrayRandomIndex($array) + { + return mt_rand(0, count($array) - 1); } /** * Array Random * * @deprecated Use ArrayUtil::arrayRandom(). - * @access public - * @param $array - * + * @param $array * @return mixed */ public static function array_random($array) diff --git a/src/Curl/BaseCurl.php b/src/Curl/BaseCurl.php new file mode 100644 index 0000000000..54d4a5072e --- /dev/null +++ b/src/Curl/BaseCurl.php @@ -0,0 +1,422 @@ +beforeSendCallback = $callback; + } + + abstract public function close(); + + /** + * Complete + * + * @param $callback callable|null + */ + public function complete($callback) + { + $this->completeCallback = $callback; + } + + /** + * Disable Timeout + */ + public function disableTimeout() + { + $this->setTimeout(null); + } + + /** + * Error + * + * @param $callback callable|null + */ + public function error($callback) + { + $this->errorCallback = $callback; + } + + /** + * Get Opt + * + * @param $option + * @return mixed + */ + public function getOpt($option) + { + return $this->options[$option] ?? null; + } + + /** + * Remove Header + * + * Remove an internal header from the request. + * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. + * + * @param $key + */ + public function removeHeader($key) + { + $this->setHeader($key, ''); + } + + /** + * Set auto referer + * + * @param mixed $auto_referer + */ + public function setAutoReferer($auto_referer = true) + { + $this->setAutoReferrer($auto_referer); + } + + /** + * Set auto referrer + * + * @param mixed $auto_referrer + */ + public function setAutoReferrer($auto_referrer = true) + { + $this->setOpt(CURLOPT_AUTOREFERER, $auto_referrer); + } + + /** + * Set Basic Authentication + * + * @param $username + * @param $password + */ + public function setBasicAuthentication($username, $password = '') + { + $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); + } + + /** + * Set Connect Timeout + * + * @param $seconds + */ + public function setConnectTimeout($seconds) + { + $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); + } + + abstract public function setCookie($key, $value); + abstract public function setCookieFile($cookie_file); + abstract public function setCookieJar($cookie_jar); + abstract public function setCookieString($string); + abstract public function setCookies($cookies); + + /** + * Set Digest Authentication + * + * @param $username + * @param $password + */ + public function setDigestAuthentication($username, $password = '') + { + $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); + } + + /** + * After Send + * + * This function is called after the request has been sent. + * + * It can be used to override whether or not the request errored. The + * instance is passed as the first argument to the function and the instance + * has attributes like $instance->httpStatusCode and $instance->response to + * help decide if the request errored. Set $instance->error to true or false + * within the function. + * + * When $instance->error is true indicating a request error, the error + * callback set by Curl::error() is called. When $instance->error is false, + * the success callback set by Curl::success() is called. + * + * @param $callback callable|null + */ + public function afterSend($callback) + { + $this->afterSendCallback = $callback; + } + + /** + * Set File + * + * @param $file + */ + public function setFile($file) + { + $this->setOpt(CURLOPT_FILE, $file); + } + + protected function setFileInternal($file) + { + $this->setOptInternal(CURLOPT_FILE, $file); + } + + /** + * Set follow location + * + * @param mixed $follow_location + * @see Curl::setMaximumRedirects() + */ + public function setFollowLocation($follow_location = true) + { + $this->setOpt(CURLOPT_FOLLOWLOCATION, $follow_location); + } + + /** + * Set forbid reuse + * + * @param mixed $forbid_reuse + */ + public function setForbidReuse($forbid_reuse = true) + { + $this->setOpt(CURLOPT_FORBID_REUSE, $forbid_reuse); + } + + abstract public function setHeader($key, $value); + abstract public function setHeaders($headers); + + /** + * Set Interface + * + * The name of the outgoing network interface to use. + * This can be an interface name, an IP address or a host name. + * + * @param $interface + */ + public function setInterface($interface) + { + $this->setOpt(CURLOPT_INTERFACE, $interface); + } + + abstract public function setJsonDecoder($mixed); + + /** + * Set maximum redirects + * + * @param mixed $maximum_redirects + * @see Curl::setFollowLocation() + */ + public function setMaximumRedirects($maximum_redirects) + { + $this->setOpt(CURLOPT_MAXREDIRS, $maximum_redirects); + } + + abstract public function setOpt($option, $value); + + protected function setOptInternal($option, $value) + { + } + + abstract public function setOpts($options); + + /** + * Set Port + * + * @param $port + */ + public function setPort($port) + { + $this->setOpt(CURLOPT_PORT, (int) $port); + } + + /** + * Set Proxy + * + * Set an HTTP proxy to tunnel requests through. + * + * @param $proxy - The HTTP proxy to tunnel requests through. May include port number. + * @param $port - The port number of the proxy to connect to. This port number can also be set in $proxy. + * @param $username - The username to use for the connection to the proxy. + * @param $password - The password to use for the connection to the proxy. + */ + public function setProxy($proxy, $port = null, $username = null, $password = null) + { + $this->setOpt(CURLOPT_PROXY, $proxy); + if ($port !== null) { + $this->setOpt(CURLOPT_PROXYPORT, $port); + } + if ($username !== null && $password !== null) { + $this->setOpt(CURLOPT_PROXYUSERPWD, $username . ':' . $password); + } + } + + /** + * Set Proxy Auth + * + * Set the HTTP authentication method(s) to use for the proxy connection. + * + * @param $auth + */ + public function setProxyAuth($auth) + { + $this->setOpt(CURLOPT_PROXYAUTH, $auth); + } + + /** + * Set Proxy Tunnel + * + * Set the proxy to tunnel through HTTP proxy. + * + * @param $tunnel boolean + */ + public function setProxyTunnel($tunnel = true) + { + $this->setOpt(CURLOPT_HTTPPROXYTUNNEL, $tunnel); + } + + /** + * Set Proxy Type + * + * Set the proxy protocol type. + * + * @param $type + */ + public function setProxyType($type) + { + $this->setOpt(CURLOPT_PROXYTYPE, $type); + } + + /** + * Set Range + * + * @param $range + */ + public function setRange($range) + { + $this->setOpt(CURLOPT_RANGE, $range); + } + + protected function setRangeInternal($range) + { + $this->setOptInternal(CURLOPT_RANGE, $range); + } + + /** + * Set Referer + * + * @param $referer + */ + public function setReferer($referer) + { + $this->setReferrer($referer); + } + + /** + * Set Referrer + * + * @param $referrer + */ + public function setReferrer($referrer) + { + $this->setOpt(CURLOPT_REFERER, $referrer); + } + + abstract public function setRetry($mixed); + + /** + * Set Timeout + * + * @param $seconds + */ + public function setTimeout($seconds) + { + $this->setOpt(CURLOPT_TIMEOUT, $seconds); + } + + protected function setTimeoutInternal($seconds) + { + $this->setOptInternal(CURLOPT_TIMEOUT, $seconds); + } + + abstract public function setUrl($url, $mixed_data = ''); + + /** + * Set User Agent + * + * @param $user_agent + */ + public function setUserAgent($user_agent) + { + $this->setOpt(CURLOPT_USERAGENT, $user_agent); + } + + protected function setUserAgentInternal($user_agent) + { + $this->setOptInternal(CURLOPT_USERAGENT, $user_agent); + } + + abstract public function setXmlDecoder($mixed); + abstract public function stop(); + + /** + * Success + * + * @param $callback callable|null + */ + public function success($callback) + { + $this->successCallback = $callback; + } + + abstract public function unsetHeader($key); + + /** + * Unset Proxy + * + * Disable use of the proxy. + */ + public function unsetProxy() + { + $this->setOpt(CURLOPT_PROXY, null); + } + + /** + * Verbose + * + * @param bool $on + * @param resource|string $output + */ + public function verbose($on = true, $output = 'STDERR') + { + if ($output === 'STDERR') { + if (defined('STDERR')) { + $output = STDERR; + } else { + $output = fopen('php://stderr', 'wb'); + } + } + + // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side + // effect of causing Curl::requestHeaders to be empty. + if ($on) { + $this->setOpt(CURLINFO_HEADER_OUT, false); + } + $this->setOpt(CURLOPT_VERBOSE, $on); + $this->setOpt(CURLOPT_STDERR, $output); + } +} diff --git a/src/Curl/CaseInsensitiveArray.php b/src/Curl/CaseInsensitiveArray.php index 7dda38f11b..bc2daca0e3 100644 --- a/src/Curl/CaseInsensitiveArray.php +++ b/src/Curl/CaseInsensitiveArray.php @@ -1,12 +1,17 @@ - $value) { @@ -55,15 +58,12 @@ public function __construct(array $initial = null) * stores the case-sensitive offset and the data at the lowercase indexes in * $this->keys and @this->data. * - * @see https://secure.php.net/manual/en/arrayaccess.offsetset.php - * - * @param string $offset The offset to store the data at (case-insensitive). - * @param mixed $value The data to store at the specified offset. - * + * @param string $offset The offset to store the data at (case-insensitive). + * @param mixed $value The data to store at the specified offset. * @return void - * - * @access public + * @see https://secure.php.net/manual/en/arrayaccess.offsetset.php */ + #[\Override] #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { @@ -82,18 +82,15 @@ public function offsetSet($offset, $value) * Checks if the offset exists in data storage. The index is looked up with * the lowercase version of the provided offset. * + * @param string $offset Offset to check + * @return bool If the offset exists. * @see https://secure.php.net/manual/en/arrayaccess.offsetexists.php - * - * @param string $offset Offset to check - * - * @return bool If the offset exists. - * - * @access public */ + #[\Override] #[\ReturnTypeWillChange] public function offsetExists($offset) { - return (bool) array_key_exists(strtolower($offset), $this->data); + return array_key_exists(strtolower($offset), $this->data); } /** @@ -102,14 +99,11 @@ public function offsetExists($offset) * Unsets the specified offset. Converts the provided offset to lowercase, * and unsets the case-sensitive key, as well as the stored data. * - * @see https://secure.php.net/manual/en/arrayaccess.offsetunset.php - * - * @param string $offset The offset to unset. - * + * @param string $offset The offset to unset. * @return void - * - * @access public + * @see https://secure.php.net/manual/en/arrayaccess.offsetunset.php */ + #[\Override] #[\ReturnTypeWillChange] public function offsetUnset($offset) { @@ -124,49 +118,38 @@ public function offsetUnset($offset) * Return the stored data at the provided offset. The offset is converted to * lowercase and the lookup is done on the data store directly. * + * @param string $offset Offset to lookup. + * @return mixed The data stored at the offset. * @see https://secure.php.net/manual/en/arrayaccess.offsetget.php - * - * @param string $offset Offset to lookup. - * - * @return mixed The data stored at the offset. - * - * @access public */ + #[\Override] #[\ReturnTypeWillChange] public function offsetGet($offset) { $offsetlower = strtolower($offset); - return isset($this->data[$offsetlower]) ? $this->data[$offsetlower] : null; + return $this->data[$offsetlower] ?? null; } /** * Count * - * @see https://secure.php.net/manual/en/countable.count.php - * - * @param void - * * @return int The number of elements stored in the array. - * - * @access public + * @see https://secure.php.net/manual/en/countable.count.php */ + #[\Override] #[\ReturnTypeWillChange] public function count() { - return (int) count($this->data); + return count($this->data); } /** * Current * - * @see https://secure.php.net/manual/en/iterator.current.php - * - * @param void - * * @return mixed Data at the current position. - * - * @access public + * @see https://secure.php.net/manual/en/iterator.current.php */ + #[\Override] #[\ReturnTypeWillChange] public function current() { @@ -176,14 +159,10 @@ public function current() /** * Next * - * @see https://secure.php.net/manual/en/iterator.next.php - * - * @param void - * * @return void - * - * @access public + * @see https://secure.php.net/manual/en/iterator.next.php */ + #[\Override] #[\ReturnTypeWillChange] public function next() { @@ -193,47 +172,37 @@ public function next() /** * Key * - * @see https://secure.php.net/manual/en/iterator.key.php - * - * @param void - * * @return mixed Case-sensitive key at current position. - * - * @access public + * @see https://secure.php.net/manual/en/iterator.key.php */ + #[\Override] #[\ReturnTypeWillChange] public function key() { $key = key($this->data); - return isset($this->keys[$key]) ? $this->keys[$key] : $key; + return $this->keys[$key] ?? $key; } /** * Valid * - * @see https://secure.php.net/manual/en/iterator.valid.php - * * @return bool If the current position is valid. - * - * @access public + * @see https://secure.php.net/manual/en/iterator.valid.php */ + #[\Override] #[\ReturnTypeWillChange] public function valid() { - return (bool) (key($this->data) !== null); + return (key($this->data) !== null); } /** * Rewind * - * @see https://secure.php.net/manual/en/iterator.rewind.php - * - * @param void - * * @return void - * - * @access public + * @see https://secure.php.net/manual/en/iterator.rewind.php */ + #[\Override] #[\ReturnTypeWillChange] public function rewind() { diff --git a/src/Curl/Curl.php b/src/Curl/Curl.php index 8123e6f8f0..83adcafe59 100644 --- a/src/Curl/Curl.php +++ b/src/Curl/Curl.php @@ -1,15 +1,13 @@ -curl = curl_init(); - $this->initialize($base_url); - } + unset($this->deferredValues['curlErrorCodeConstant']); + unset($this->deferredValues['curlErrorCodeConstants']); + unset($this->deferredValues['curlOptionCodeConstants']); + unset($this->deferredValues['effectiveUrl']); + unset($this->deferredValues['rfc2616']); + unset($this->deferredValues['rfc6265']); + unset($this->deferredValues['totalTime']); - /** - * Before Send - * - * @access public - * @param $callback callable|null - */ - public function beforeSend($callback) - { - $this->beforeSendCallback = $callback; + $this->curl = curl_init(); + $this->initialize($base_url, $options); } /** * Build Post Data * - * @access public - * @param $data - * + * @param $data * @return array|string * @throws \ErrorException */ @@ -145,7 +140,8 @@ public function buildPostData($data) $binary_data = false; // Return JSON-encoded string when the request's content-type is JSON and the data is serializable. - if (isset($this->headers['Content-Type']) && + if ( + isset($this->headers['Content-Type']) && preg_match($this->jsonPattern, $this->headers['Content-Type']) && ( is_array($data) || @@ -154,7 +150,8 @@ public function buildPostData($data) interface_exists('JsonSerializable', false) && $data instanceof \JsonSerializable ) - )) { + ) + ) { $data = \Curl\Encoder::encodeJson($data); } elseif (is_array($data)) { // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch, @@ -176,17 +173,44 @@ interface_exists('JsonSerializable', false) && } } elseif ($value instanceof \CURLFile) { $binary_data = true; + } elseif ($value instanceof \CURLStringFile) { + $binary_data = true; } } } - if (!$binary_data && + if ( + !$binary_data && (is_array($data) || is_object($data)) && ( !isset($this->headers['Content-Type']) || !preg_match('/^multipart\/form-data/', $this->headers['Content-Type']) - )) { - $data = http_build_query($data, '', '&'); + ) + ) { + // Avoid using http_build_query() as keys with null values are + // unexpectedly excluded from the resulting string. + // + // $ php -a + // php > echo http_build_query(['a' => '1', 'b' => null, 'c' => '3']); + // a=1&c=3 + // php > echo http_build_query(['a' => '1', 'b' => '', 'c' => '3']); + // a=1&b=&c=3 + // + // $data = http_build_query($data, '', '&'); + $data = implode('&', array_map(function ($k, $v) { + // Encode keys and values using urlencode() to match the default + // behavior http_build_query() where $encoding_type is + // PHP_QUERY_RFC1738. + // + // Use strval() as urlencode() expects a string parameter: + // TypeError: urlencode() expects parameter 1 to be string, integer given + // TypeError: urlencode() expects parameter 1 to be string, null given + // + // php_raw_url_encode() + // php_url_encode() + // https://github.com/php/php-src/blob/master/ext/standard/http.c + return urlencode(strval($k)) . '=' . urlencode(strval($v)); + }, array_keys((array)$data), array_values((array)$data))); } return $data; @@ -194,8 +218,6 @@ interface_exists('JsonSerializable', false) && /** * Call - * - * @access public */ public function call() { @@ -209,39 +231,25 @@ public function call() /** * Close - * - * @access public */ + #[\Override] public function close() { - if (is_resource($this->curl) || $this->curl instanceof \CurlHandle) { - curl_close($this->curl); - } $this->curl = null; $this->options = null; + $this->userSetOptions = null; $this->jsonDecoder = null; $this->jsonDecoderArgs = null; $this->xmlDecoder = null; $this->xmlDecoderArgs = null; + $this->headerCallbackData = null; $this->defaultDecoder = null; } - /** - * Complete - * - * @access public - * @param $callback callable|null - */ - public function complete($callback) - { - $this->completeCallback = $callback; - } - /** * Progress * - * @access public - * @param $callback callable|null + * @param $callback callable|null */ public function progress($callback) { @@ -249,17 +257,27 @@ public function progress($callback) $this->setOpt(CURLOPT_NOPROGRESS, false); } + private function progressInternal($callback) + { + $this->setOptInternal(CURLOPT_PROGRESSFUNCTION, $callback); + $this->setOptInternal(CURLOPT_NOPROGRESS, false); + } + /** * Delete * - * @access public - * @param $url - * @param $query_parameters - * @param $data - * + * @param $url + * @param $query_parameters + * @param $data * @return mixed Returns the value provided by exec. */ public function delete($url, $query_parameters = [], $data = []) + { + $this->setDelete($url, $query_parameters, $data); + return $this->exec(); + } + + public function setDelete($url, $query_parameters = [], $data = []) { if (is_array($url)) { $data = $query_parameters; @@ -280,17 +298,14 @@ public function delete($url, $query_parameters = [], $data = []) if (!empty($data)) { $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); } - return $this->exec(); } /** * Download * - * @access public - * @param $url - * @param $mixed_filename - * - * @return boolean + * @param $url + * @param $mixed_filename + * @return bool */ public function download($url, $mixed_filename) { @@ -312,7 +327,7 @@ public function download($url, $mixed_filename) // Attempt to resume download only when a temporary download file exists and is not empty. if (is_file($download_filename) && $filesize = filesize($download_filename)) { $first_byte_position = $filesize; - $range = $first_byte_position . '-'; + $range = (string)$first_byte_position . '-'; $this->setRange($range); $this->fileHandle = fopen($download_filename, 'ab'); } else { @@ -339,120 +354,127 @@ public function download($url, $mixed_filename) /** * Fast download * - * @access private - * @param $url - * @param $filename - * @param $connections - * - * @return boolean - */ - public function _fastDownload($url, $filename, $connections = 4) { - // First we need to retrive the 'Content-Length' header. - // Use GET because not all hosts support HEAD requests. - $this->setOpts([ - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_NOBODY => true, - CURLOPT_HEADER => true, - CURLOPT_ENCODING => '', - ]); - $this->setUrl($url); - $this->exec(); + * @param $url + * @param $filename + * @param $connections + * @return bool + */ + public function fastDownload($url, $filename, $connections = 4) + { + // Retrieve content length from the "Content-Length" header from the url + // to download. Use an HTTP GET request without a body instead of a HEAD + // request because not all hosts support HEAD requests. + $curl = new Curl(); + $curl->setOptInternal(CURLOPT_NOBODY, true); + + // Pass user-specified options to the instance checking for content-length. + $curl->setOpts($this->userSetOptions); + $curl->get($url); - $content_length = isset($this->responseHeaders['Content-Length']) ? - $this->responseHeaders['Content-Length'] : null; + // Exit early when an error occurred. + if ($curl->error) { + return false; + } - // If content length header is missing, use the normal download. + $content_length = $curl->responseHeaders['Content-Length'] ?? null; + + // Use a regular download when content length could not be determined. if (!$content_length) { return $this->download($url, $filename); } - // Try to divide chunk_size equally. - $chunkSize = ceil($content_length / $connections); - - // First bytes. - $offset = 0; - $nextChunk = $chunkSize; + // Divide chunk_size across the number of connections. + $chunk_size = (int)ceil($content_length / $connections); - // We need this later. - $file_parts = []; + // Keep track of file name parts. + $part_file_names = []; $multi_curl = new MultiCurl(); $multi_curl->setConcurrency($connections); - $multi_curl->error(function ($instance) { - return false; - }); + for ($part_number = 1; $part_number <= $connections; $part_number++) { + $range_start = ($part_number - 1) * $chunk_size; + $range_end = $range_start + $chunk_size - 1; + if ($part_number === $connections) { + $range_end = ''; + } + $range = (string)$range_start . '-' . (string)$range_end; + + $part_file_name = $filename . '.part' . (string)$part_number; - for ($i = 1; $i <= $connections; $i++) { - // If last chunk then no need to supply it. - // Range starts with 0, so subtract 1. - $nextChunk = $i === $connections ? '' : $nextChunk - 1; + // Save the file name of this part. + $part_file_names[] = $part_file_name; - // Create part file. - $fpath = "$filename.part$i"; - if (is_file($fpath)) { - unlink($fpath); + // Remove any existing file part. + if (is_file($part_file_name)) { + unlink($part_file_name); } - $fp = fopen($fpath, 'w'); - // Track all fileparts names; we need this later. - $file_parts[] = $fpath; + // Create file part. + $file_handle = tmpfile(); + // Setup the instance downloading a part. $curl = new Curl(); - $curl->setOpt(CURLOPT_ENCODING, ''); - $curl->setRange("$offset-$nextChunk"); - $curl->setFile($fp); - $curl->disableTimeout(); // otherwise download may fail. $curl->setUrl($url); - $curl->complete(function () use ($fp) { - fclose($fp); - }); + // Pass user-specified options to the instance downloading a part. + $curl->setOpts($this->userSetOptions); - $multi_curl->addCurl(http://github.com/$curl); + $curl->setOptInternal(CURLOPT_CUSTOMREQUEST, 'GET'); + $curl->setOptInternal(CURLOPT_HTTPGET, true); + $curl->setRangeInternal($range); + $curl->setFileInternal($file_handle); + $curl->fileHandle = $file_handle; - if ($i !== $connections) { - $offset = $nextChunk + 1; // Add 1 to match offset. - $nextChunk = $nextChunk + $chunkSize; - } + $curl->downloadCompleteCallback = function ($instance, $tmpfile) use ($part_file_name) { + $fh = fopen($part_file_name, 'wb'); + if ($fh !== false) { + stream_copy_to_stream($tmpfile, $fh); + fclose($fh); + } + }; + + $multi_curl->addCurl(http://github.com/$curl); } - // let the magic begin. + // Start the simultaneous downloads for each of the ranges in parallel. $multi_curl->start(); - // Concatenate chunks to single. + // Remove existing download file name at destination. if (is_file($filename)) { unlink($filename); } - $mainfp = fopen($filename, 'w'); - foreach ($file_parts as $part) { - $fp = fopen($part, 'r'); - stream_copy_to_stream($fp, $mainfp); - fclose($fp); - unlink($part); + + // Combine downloaded chunks into a single file. + $main_file_handle = fopen($filename, 'w'); + if ($main_file_handle === false) { + return false; } - fclose($mainfp); - return true; - } + foreach ($part_file_names as $part_file_name) { + if (!is_file($part_file_name)) { + return false; + } - /** - * Error - * - * @access public - * @param $callback callable|null - */ - public function error($callback) - { - $this->errorCallback = $callback; + $file_handle = fopen($part_file_name, 'r'); + if ($file_handle === false) { + return false; + } + + stream_copy_to_stream($file_handle, $main_file_handle); + fclose($file_handle); + unlink($part_file_name); + } + + fclose($main_file_handle); + + return true; } /** * Exec * - * @access public - * @param $ch - * + * @param $ch * @return mixed Returns the value provided by parseResponse. */ public function exec($ch = null) @@ -495,16 +517,18 @@ public function exec($ch = null) // Include additional error code information in error message when possible. if ($this->curlError) { - $this->curlErrorMessage = - curl_strerror($this->curlErrorCode) . ( - empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage - ); - } + $curl_error_message = curl_strerror($this->curlErrorCode); - $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); - $this->httpError = in_array((int) floor($this->httpStatusCode / 100), [4, 5], true); - $this->error = $this->curlError || $this->httpError; - $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; + if (isset($this->curlErrorCodeConstant)) { + $curl_error_message .= ' (' . $this->curlErrorCodeConstant . ')'; + } + + if (!empty($this->curlErrorMessage)) { + $curl_error_message .= ': ' . $this->curlErrorMessage; + } + + $this->curlErrorMessage = $curl_error_message; + } // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);). @@ -514,6 +538,18 @@ public function exec($ch = null) $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders); $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse); + $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); + $this->httpError = in_array((int) floor($this->httpStatusCode / 100), [4, 5], true); + $this->error = $this->curlError || $this->httpError; + + $this->call($this->afterSendCallback); + + if (!in_array($this->error, [true, false], true)) { + trigger_error('$instance->error MUST be set to true or false', E_USER_WARNING); + } + + $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; + $this->httpErrorMessage = ''; if ($this->error) { if (isset($this->responseHeaders['Status-Line'])) { @@ -523,14 +559,15 @@ public function exec($ch = null) $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; // Reset select deferred properties so that they may be recalculated. - unset($this->effectiveUrl); - unset($this->totalTime); + unset($this->deferredValues['curlErrorCodeConstant']); + unset($this->deferredValues['effectiveUrl']); + unset($this->deferredValues['totalTime']); // Reset content-length header possibly set from a PUT or SEARCH request. $this->unsetHeader('Content-Length'); // Reset nobody setting possibly set from a HEAD request. - $this->setOpt(CURLOPT_NOBODY, false); + $this->setOptInternal(CURLOPT_NOBODY, false); // Allow multicurl to attempt retry as needed. if ($this->isChildOfMultiCurl()) { @@ -565,30 +602,31 @@ public function execDone() /** * Get * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function get($url, $data = []) + { + $this->setGet($url, $data); + return $this->exec(); + } + + public function setGet($url, $data = []) { if (is_array($url)) { $data = $url; $url = (string)$this->url; } $this->setUrl($url, $data); - $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); - $this->setOpt(CURLOPT_HTTPGET, true); - return $this->exec(); + $this->setOptInternal(CURLOPT_CUSTOMREQUEST, 'GET'); + $this->setOptInternal(CURLOPT_HTTPGET, true); } /** * Get Info * - * @access public - * @param $opt - * + * @param $opt * @return mixed */ public function getInfo($opt = null) @@ -603,29 +641,20 @@ public function getInfo($opt = null) return call_user_func_array('curl_getinfo', $args); } - /** - * Get Opt - * - * @access public - * @param $option - * - * @return mixed - */ - public function getOpt($option) - { - return isset($this->options[$option]) ? $this->options[$option] : null; - } - /** * Head * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function head($url, $data = []) + { + $this->setHead($url, $data); + return $this->exec(); + } + + public function setHead($url, $data = []) { if (is_array($url)) { $data = $url; @@ -634,19 +663,22 @@ public function head($url, $data = []) $this->setUrl($url, $data); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); $this->setOpt(CURLOPT_NOBODY, true); - return $this->exec(); } /** * Options * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function options($url, $data = []) + { + $this->setOptions($url, $data); + return $this->exec(); + } + + public function setOptions($url, $data = []) { if (is_array($url)) { $data = $url; @@ -654,19 +686,22 @@ public function options($url, $data = []) } $this->setUrl($url, $data); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); - return $this->exec(); } /** * Patch * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function patch($url, $data = []) + { + $this->setPatch($url, $data); + return $this->exec(); + } + + public function setPatch($url, $data = []) { if (is_array($url)) { $data = $url; @@ -680,28 +715,28 @@ public function patch($url, $data = []) $this->setUrl($url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); - return $this->exec(); } /** * Post * - * @access public - * @param $url - * @param $data - * @param $follow_303_with_post - * If true, will cause 303 redirections to be followed using a POST request (default: false). - * Notes: - * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. - * - According to the HTTP specs (see [1]), a 303 redirection should be followed using - * the GET method. 301 and 302 must not. - * - In order to force a 303 redirection to be performed using the same method, the - * underlying cURL object must be set in a special state (the CURLOPT_CUSTOMREQUEST - * option must be set to the method to use after the redirection). Due to a limitation - * of the cURL extension of PHP < 5.5.11 ([2], [3]), it is not possible to reset this - * option. Using these PHP engines, it is therefore impossible to restore this behavior - * on an existing php-curl-class Curl object. - * + * @param $url + * @param $data + * @param $follow_303_with_post + * If true, will cause 303 redirections to be followed using a POST request + * (default: false). + * Notes: + * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set + * to true. + * - According to the HTTP specs (see [1]), a 303 redirection should be followed + * using the GET method. 301 and 302 must not. + * - In order to force a 303 redirection to be performed using the same method, + * the underlying cURL object must be set in a special state (the + * CURLOPT_CUSTOMREQUEST option must be set to the method to use after the + * redirection). Due to a limitation of the cURL extension of PHP < 5.5.11 ([2], + * [3]), it is not possible to reset this option. Using these PHP engines, it is + * therefore impossible to restore this behavior on an existing php-curl-class + * Curl object. * @return mixed Returns the value provided by exec. * * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2 @@ -709,6 +744,12 @@ public function patch($url, $data = []) * [3] http://php.net/ChangeLog-5.php#5.5.11 */ public function post($url, $data = '', $follow_303_with_post = false) + { + $this->setPost($url, $data, $follow_303_with_post); + return $this->exec(); + } + + public function setPost($url, $data = '', $follow_303_with_post = false) { if (is_array($url)) { $follow_303_with_post = (bool)$data; @@ -733,19 +774,22 @@ public function post($url, $data = '', $follow_303_with_post = false) $this->setOpt(CURLOPT_POST, true); $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); - return $this->exec(); } /** * Put * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function put($url, $data = []) + { + $this->setPut($url, $data); + return $this->exec(); + } + + public function setPut($url, $data = []) { if (is_array($url)) { $data = $url; @@ -762,19 +806,22 @@ public function put($url, $data = []) if (!empty($put_data)) { $this->setOpt(CURLOPT_POSTFIELDS, $put_data); } - return $this->exec(); } /** * Search * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return mixed Returns the value provided by exec. */ public function search($url, $data = []) + { + $this->setSearch($url, $data); + return $this->exec(); + } + + public function setSearch($url, $data = []) { if (is_array($url)) { $data = $url; @@ -791,42 +838,15 @@ public function search($url, $data = []) if (!empty($put_data)) { $this->setOpt(CURLOPT_POSTFIELDS, $put_data); } - return $this->exec(); - } - - /** - * Set Basic Authentication - * - * @access public - * @param $username - * @param $password - */ - public function setBasicAuthentication($username, $password = '') - { - $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); - } - - /** - * Set Digest Authentication - * - * @access public - * @param $username - * @param $password - */ - public function setDigestAuthentication($username, $password = '') - { - $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); } /** * Set Cookie * - * @access public - * @param $key - * @param $value + * @param $key + * @param $value */ + #[\Override] public function setCookie($key, $value) { $this->setEncodedCookie($key, $value); @@ -836,9 +856,9 @@ public function setCookie($key, $value) /** * Set Cookies * - * @access public - * @param $cookies + * @param $cookies */ + #[\Override] public function setCookies($cookies) { foreach ($cookies as $key => $value) { @@ -850,9 +870,7 @@ public function setCookies($cookies) /** * Get Cookie * - * @access public - * @param $key - * + * @param $key * @return mixed */ public function getCookie($key) @@ -863,21 +881,18 @@ public function getCookie($key) /** * Get Response Cookie * - * @access public - * @param $key - * + * @param $key * @return mixed */ public function getResponseCookie($key) { - return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null; + return $this->responseCookies[$key] ?? null; } /** * Set Max Filesize * - * @access public - * @param $bytes + * @param $bytes */ public function setMaxFilesize($bytes) { @@ -888,36 +903,13 @@ public function setMaxFilesize($bytes) $this->progress($callback); } - /** - * Set Port - * - * @access public - * @param $port - */ - public function setPort($port) - { - $this->setOpt(CURLOPT_PORT, (int) $port); - } - - /** - * Set Connect Timeout - * - * @access public - * @param $seconds - */ - public function setConnectTimeout($seconds) - { - $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); - } - /** * Set Cookie String * - * @access public - * @param $string - * + * @param $string * @return bool */ + #[\Override] public function setCookieString($string) { return $this->setOpt(CURLOPT_COOKIE, $string); @@ -926,11 +918,10 @@ public function setCookieString($string) /** * Set Cookie File * - * @access public - * @param $cookie_file - * - * @return boolean + * @param $cookie_file + * @return bool */ + #[\Override] public function setCookieFile($cookie_file) { return $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); @@ -939,11 +930,10 @@ public function setCookieFile($cookie_file) /** * Set Cookie Jar * - * @access public - * @param $cookie_jar - * - * @return boolean + * @param $cookie_jar + * @return bool */ + #[\Override] public function setCookieJar($cookie_jar) { return $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); @@ -952,10 +942,9 @@ public function setCookieJar($cookie_jar) /** * Set Default JSON Decoder * - * @access public - * @param $assoc - * @param $depth - * @param $options + * @param $assoc + * @param $depth + * @param $options */ public function setDefaultJsonDecoder() { @@ -966,11 +955,10 @@ public function setDefaultJsonDecoder() /** * Set Default XML Decoder * - * @access public - * @param $class_name - * @param $options - * @param $ns - * @param $is_prefix + * @param $class_name + * @param $options + * @param $ns + * @param $is_prefix */ public function setDefaultXmlDecoder() { @@ -981,57 +969,67 @@ public function setDefaultXmlDecoder() /** * Set Default Decoder * - * @access public - * @param $mixed boolean|callable|string + * @param $mixed boolean|callable|string */ public function setDefaultDecoder($mixed = 'json') { if ($mixed === false) { $this->defaultDecoder = false; + } elseif ($mixed === 'json') { + $this->defaultDecoder = '\Curl\Decoder::decodeJson'; + } elseif ($mixed === 'xml') { + $this->defaultDecoder = '\Curl\Decoder::decodeXml'; } elseif (is_callable($mixed)) { $this->defaultDecoder = $mixed; - } else { - if ($mixed === 'json') { - $this->defaultDecoder = '\Curl\Decoder::decodeJson'; - } elseif ($mixed === 'xml') { - $this->defaultDecoder = '\Curl\Decoder::decodeXml'; - } } } + /** + * Set Default Header Out + */ + public function setDefaultHeaderOut() + { + $this->setOpt(CURLINFO_HEADER_OUT, true); + } + + private function setDefaultHeaderOutInternal() + { + $this->setOptInternal(CURLINFO_HEADER_OUT, true); + } + /** * Set Default Timeout - * - * @access public */ public function setDefaultTimeout() { $this->setTimeout(self::DEFAULT_TIMEOUT); } + private function setDefaultTimeoutInternal() + { + $this->setTimeoutInternal(self::DEFAULT_TIMEOUT); + } + /** * Set Default User Agent - * - * @access public */ public function setDefaultUserAgent() + { + $this->setUserAgent($this->getDefaultUserAgent()); + } + + private function setDefaultUserAgentInternal() + { + $this->setUserAgentInternal($this->getDefaultUserAgent()); + } + + private function getDefaultUserAgent() { $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)'; $user_agent .= ' PHP/' . PHP_VERSION; $curl_version = curl_version(); $user_agent .= ' curl/' . $curl_version['version']; - $this->setUserAgent($user_agent); - } - - /** - * Set File - * - * @access public - * @param $file - */ - public function setFile($file) - { - $this->setOpt(CURLOPT_FILE, $file); + return $user_agent; } /** @@ -1039,10 +1037,10 @@ public function setFile($file) * * Add extra header to include in the request. * - * @access public - * @param $key - * @param $value + * @param $key + * @param $value */ + #[\Override] public function setHeader($key, $value) { $this->headers[$key] = $value; @@ -1058,9 +1056,9 @@ public function setHeader($key, $value) * * Add extra headers to include in the request. * - * @access public - * @param $headers + * @param $headers */ + #[\Override] public function setHeaders($headers) { if (ArrayUtil::isArrayAssoc($headers)) { @@ -1071,7 +1069,7 @@ public function setHeaders($headers) } } else { foreach ($headers as $header) { - list($key, $value) = explode(':', $header, 2); + list($key, $value) = array_pad(explode(':', $header, 2), 2, ''); $key = trim($key); $value = trim($value); $this->headers[$key] = $value; @@ -1089,9 +1087,9 @@ public function setHeaders($headers) /** * Set JSON Decoder * - * @access public - * @param $mixed boolean|callable + * @param $mixed boolean|callable */ + #[\Override] public function setJsonDecoder($mixed) { if ($mixed === false || is_callable($mixed)) { @@ -1103,9 +1101,9 @@ public function setJsonDecoder($mixed) /** * Set XML Decoder * - * @access public - * @param $mixed boolean|callable + * @param $mixed boolean|callable */ + #[\Override] public function setXmlDecoder($mixed) { if ($mixed === false || is_callable($mixed)) { @@ -1117,12 +1115,11 @@ public function setXmlDecoder($mixed) /** * Set Opt * - * @access public - * @param $option - * @param $value - * - * @return boolean + * @param $option + * @param $value + * @return bool */ + #[\Override] public function setOpt($option, $value) { $required_options = [ @@ -1136,134 +1133,68 @@ public function setOpt($option, $value) $success = curl_setopt($this->curl, $option, $value); if ($success) { $this->options[$option] = $value; + $this->userSetOptions[$option] = $value; } return $success; } /** - * Set Opts - * - * @access public - * @param $options + * Set Opt Internal * - * @return boolean - * Returns true if all options were successfully set. If an option could not be successfully set, false is - * immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array(). + * @param $option + * @param $value + * @return bool */ - public function setOpts($options) + #[\Override] + protected function setOptInternal($option, $value) { - foreach ($options as $option => $value) { - if (!$this->setOpt($option, $value)) { - return false; - } + $success = curl_setopt($this->curl, $option, $value); + if ($success) { + $this->options[$option] = $value; } - return true; + return $success; } /** - * Set Proxy - * - * Set an HTTP proxy to tunnel requests through. + * Set Opts * - * @access public - * @param $proxy - The HTTP proxy to tunnel requests through. May include port number. - * @param $port - The port number of the proxy to connect to. This port number can also be set in $proxy. - * @param $username - The username to use for the connection to the proxy. - * @param $password - The password to use for the connection to the proxy. + * @param $options + * @return bool + * Returns true if all options were successfully set. If an + * option could not be successfully set, false is immediately + * returned, ignoring any future options in the options array. + * Similar to curl_setopt_array(). */ - public function setProxy($proxy, $port = null, $username = null, $password = null) + #[\Override] + public function setOpts($options) { - $this->setOpt(CURLOPT_PROXY, $proxy); - if ($port !== null) { - $this->setOpt(CURLOPT_PROXYPORT, $port); + if (!count($options)) { + return true; } - if ($username !== null && $password !== null) { - $this->setOpt(CURLOPT_PROXYUSERPWD, $username . ':' . $password); + foreach ($options as $option => $value) { + if (!$this->setOpt($option, $value)) { + return false; + } } + return true; } /** - * Set Proxy Auth - * - * Set the HTTP authentication method(s) to use for the proxy connection. - * - * @access public - * @param $auth - */ - public function setProxyAuth($auth) - { - $this->setOpt(CURLOPT_PROXYAUTH, $auth); - } - - /** - * Set Proxy Type - * - * Set the proxy protocol type. - * - * @access public - * @param $type - */ - public function setProxyType($type) - { - $this->setOpt(CURLOPT_PROXYTYPE, $type); - } - - /** - * Set Proxy Tunnel - * - * Set the proxy to tunnel through HTTP proxy. - * - * @access public - * @param $tunnel boolean - */ - public function setProxyTunnel($tunnel = true) - { - $this->setOpt(CURLOPT_HTTPPROXYTUNNEL, $tunnel); - } - - /** - * Unset Proxy - * - * Disable use of the proxy. - * - * @access public - */ - public function unsetProxy() - { - $this->setOpt(CURLOPT_PROXY, null); - } - - /** - * Set Range + * Set Protocols * - * @access public - * @param $range - */ - public function setRange($range) - { - $this->setOpt(CURLOPT_RANGE, $range); - } - - /** - * Set Referer + * Limit what protocols libcurl will accept for a request. * - * @access public - * @param $referer + * @param $protocols + * @see Curl::setRedirectProtocols() */ - public function setReferer($referer) + public function setProtocols($protocols) { - $this->setReferrer($referer); + $this->setOpt(CURLOPT_PROTOCOLS, $protocols); } - /** - * Set Referrer - * - * @access public - * @param $referrer - */ - public function setReferrer($referrer) + private function setProtocolsInternal($protocols) { - $this->setOpt(CURLOPT_REFERER, $referrer); + $this->setOptInternal(CURLOPT_PROTOCOLS, $protocols); } /** @@ -1277,9 +1208,9 @@ public function setReferrer($referrer) * When using a callable decider, the request will be retried until the * function returns a value which evaluates to false. * - * @access public - * @param $mixed + * @param $mixed */ + #[\Override] public function setRetry($mixed) { if (is_callable($mixed)) { @@ -1291,33 +1222,30 @@ public function setRetry($mixed) } /** - * Set Timeout + * Set Redirect Protocols * - * @access public - * @param $seconds + * Limit what protocols libcurl will accept when following a redirect. + * + * @param $redirect_protocols + * @see Curl::setProtocols() */ - public function setTimeout($seconds) + public function setRedirectProtocols($redirect_protocols) { - $this->setOpt(CURLOPT_TIMEOUT, $seconds); + $this->setOpt(CURLOPT_REDIR_PROTOCOLS, $redirect_protocols); } - /** - * Disable Timeout - * - * @access public - */ - public function disableTimeout() + private function setRedirectProtocolsInternal($redirect_protocols) { - $this->setTimeout(null); + $this->setOptInternal(CURLOPT_REDIR_PROTOCOLS, $redirect_protocols); } /** * Set Url * - * @access public - * @param $url - * @param $mixed_data + * @param $url + * @param $mixed_data */ + #[\Override] public function setUrl($url, $mixed_data = '') { $built_url = Url::buildUrl($url, $mixed_data); @@ -1331,35 +1259,8 @@ public function setUrl($url, $mixed_data = '') $this->setOpt(CURLOPT_URL, $this->url); } - /** - * Set User Agent - * - * @access public - * @param $user_agent - */ - public function setUserAgent($user_agent) - { - $this->setOpt(CURLOPT_USERAGENT, $user_agent); - } - - /** - * Set Interface - * - * The name of the outgoing network interface to use. - * This can be an interface name, an IP address or a host name. - * - * @access public - * @param $interface - */ - public function setInterface($interface) - { - $this->setOpt(CURLOPT_INTERFACE, $interface); - } - /** * Attempt Retry - * - * @access public */ public function attemptRetry() { @@ -1380,25 +1281,14 @@ public function attemptRetry() return $attempt_retry; } - /** - * Success - * - * @access public - * @param $callback callable|null - */ - public function success($callback) - { - $this->successCallback = $callback; - } - /** * Unset Header * * Remove extra header previously set using Curl::setHeader(). * - * @access public - * @param $key + * @param $key */ + #[\Override] public function unsetHeader($key) { unset($this->headers[$key]); @@ -1409,50 +1299,10 @@ public function unsetHeader($key) $this->setOpt(CURLOPT_HTTPHEADER, $headers); } - /** - * Remove Header - * - * Remove an internal header from the request. - * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. - * - * @access public - * @param $key - */ - public function removeHeader($key) - { - $this->setHeader($key, ''); - } - - /** - * Verbose - * - * @access public - * @param bool $on - * @param resource|string $output - */ - public function verbose($on = true, $output = 'STDERR') - { - if ($output === 'STDERR') { - if (!defined('STDERR')) { - define('STDERR', fopen('php://stderr', 'wb')); - } - $output = STDERR; - } - - // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side - // effect of causing Curl::requestHeaders to be empty. - if ($on) { - $this->setOpt(CURLINFO_HEADER_OUT, false); - } - $this->setOpt(CURLOPT_VERBOSE, $on); - $this->setOpt(CURLOPT_STDERR, $output); - } - /** * Diagnose * - * @access public - * @param bool $return + * @param bool $return */ public function diagnose($return = false) { @@ -1471,55 +1321,102 @@ public function diagnose($return = false) if ($this->attempts === 0) { echo 'No HTTP requests have been made.' . "\n"; } else { - $request_method = $this->getOpt(CURLOPT_CUSTOMREQUEST); + $request_types = [ + 'DELETE' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'DELETE', + 'GET' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'GET' || $this->getOpt(CURLOPT_HTTPGET), + 'HEAD' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'HEAD', + 'OPTIONS' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'OPTIONS', + 'PATCH' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'PATCH', + 'POST' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'POST' || $this->getOpt(CURLOPT_POST), + 'PUT' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'PUT', + 'SEARCH' => $this->getOpt(CURLOPT_CUSTOMREQUEST) === 'SEARCH', + ]; + $request_method = ''; + foreach ($request_types as $http_method_name => $http_method_used) { + if ($http_method_used) { + $request_method = $http_method_name; + break; + } + } $request_url = $this->getOpt(CURLOPT_URL); + $request_options_count = count($this->options); $request_headers_count = count($this->requestHeaders); $request_body_empty = empty($this->getOpt(CURLOPT_POSTFIELDS)); - $response_header_length = isset($this->responseHeaders['Content-Length']) ? - $this->responseHeaders['Content-Length'] : '(not specified in response header)'; + $response_header_length = $this->responseHeaders['Content-Length'] ?? '(not specified in response header)'; $response_calculated_length = is_string($this->rawResponse) ? strlen($this->rawResponse) : '(' . var_export($this->rawResponse, true) . ')'; $response_headers_count = count($this->responseHeaders); echo - 'Sent an HTTP ' . $request_method . ' request to "' . $request_url . '".' . "\n" . - 'Request contained ' . $request_headers_count . ' ' . ( + 'Request contained ' . (string)$request_options_count . ' ' . ( + $request_options_count === 1 ? 'option:' : 'options:' + ) . "\n"; + if ($request_options_count) { + $i = 1; + foreach ($this->options as $option => $value) { + echo ' ' . (string)$i . ' '; + $this->displayCurlOptionValue($option, $value); + $i += 1; + } + } + + echo + 'Sent an HTTP ' . $request_method . ' request to "' . $request_url . '".' . "\n" . + 'Request contained ' . (string)$request_headers_count . ' ' . ( $request_headers_count === 1 ? 'header:' : 'headers:' ) . "\n"; if ($request_headers_count) { $i = 1; foreach ($this->requestHeaders as $key => $value) { - echo ' ' . $i . ' ' . $key . ': ' . $value . "\n"; + echo ' ' . (string)$i . ' ' . $key . ': ' . $value . "\n"; $i += 1; } } echo 'Request contained ' . ($request_body_empty ? 'no body' : 'a body') . '.' . "\n"; - if ($this->getOpt(CURLOPT_VERBOSE) || $this->getOpt(CURLINFO_HEADER_OUT) !== true) { + if ( + $request_headers_count === 0 && ( + $this->getOpt(CURLOPT_VERBOSE) || + !$this->getOpt(CURLINFO_HEADER_OUT) + ) + ) { echo - 'Warning: Request headers (Curl::requestHeaders) are expected be empty ' . + 'Warning: Request headers (Curl::requestHeaders) are expected to be empty ' . '(CURLOPT_VERBOSE was enabled or CURLINFO_HEADER_OUT was disabled).' . "\n"; } + if (isset($this->responseHeaders['allow'])) { + $allowed_request_types = array_map(function ($v) { + return trim($v); + }, explode(',', strtoupper($this->responseHeaders['allow']))); + foreach ($request_types as $http_method_name => $http_method_used) { + if ($http_method_used && !in_array($http_method_name, $allowed_request_types, true)) { + echo + 'Warning: An HTTP ' . $http_method_name . ' request was made, but only the following ' . + 'request types are allowed: ' . implode(', ', $allowed_request_types) . "\n"; + } + } + } + echo - 'Response contains ' . $response_headers_count . ' ' . ( + 'Response contains ' . (string)$response_headers_count . ' ' . ( $response_headers_count === 1 ? 'header:' : 'headers:' ) . "\n"; if ($this->responseHeaders !== null) { $i = 1; foreach ($this->responseHeaders as $key => $value) { - echo ' ' . $i . ' ' . $key . ': ' . $value . "\n"; + echo ' ' . (string)$i . ' ' . $key . ': ' . $value . "\n"; $i += 1; } } if (!isset($this->responseHeaders['Content-Type'])) { - echo 'Response did not set a content type' . "\n"; + echo 'Response did not set a content type.' . "\n"; } elseif (preg_match($this->jsonPattern, $this->responseHeaders['Content-Type'])) { - echo 'Response appears to be JSON' . "\n"; + echo 'Response appears to be JSON.' . "\n"; } elseif (preg_match($this->xmlPattern, $this->responseHeaders['Content-Type'])) { - echo 'Response appears to be XML' . "\n"; + echo 'Response appears to be XML.' . "\n"; } if ($this->curlError) { @@ -1545,7 +1442,38 @@ public function diagnose($return = false) if (isset($this->responseHeaders['Content-Length'])) { echo 'Response content length (from content-length header): ' . $response_header_length . "\n"; } else { - echo 'Response content length (calculated): ' . $response_calculated_length . "\n"; + echo 'Response content length (calculated): ' . (string)$response_calculated_length . "\n"; + } + + if ( + isset($this->responseHeaders['Content-Type']) && + preg_match($this->jsonPattern, $this->responseHeaders['Content-Type']) + ) { + $parsed_response = json_decode($this->rawResponse, true); + if ($parsed_response !== null) { + $messages = []; + array_walk_recursive($parsed_response, function ($value, $key) use (&$messages) { + if (in_array($key, ['code', 'error', 'message'], true)) { + $message = $key . ': ' . $value; + $messages[] = $message; + } + }); + $messages = array_unique($messages); + + $messages_count = count($messages); + if ($messages_count) { + echo + 'Found ' . (string)$messages_count . ' ' . + ($messages_count === 1 ? 'message' : 'messages') . + ' in response:' . "\n"; + + $i = 1; + foreach ($messages as $message) { + echo ' ' . (string)$i . ' ' . $message . "\n"; + $i += 1; + } + } + } } } } @@ -1562,8 +1490,6 @@ public function diagnose($return = false) /** * Reset - * - * @access public */ public function reset() { @@ -1573,57 +1499,11 @@ public function reset() $this->curl = curl_init(); } - $this->initialize(); - } - - /** - * Set auto referer - * - * @access public - */ - public function setAutoReferer($auto_referer = true) - { - $this->setAutoReferrer($auto_referer); - } + $this->setDefaultUserAgentInternal(); + $this->setDefaultTimeoutInternal(); + $this->setDefaultHeaderOutInternal(); - /** - * Set auto referrer - * - * @access public - */ - public function setAutoReferrer($auto_referrer = true) - { - $this->setOpt(CURLOPT_AUTOREFERER, $auto_referrer); - } - - /** - * Set follow location - * - * @access public - */ - public function setFollowLocation($follow_location = true) - { - $this->setOpt(CURLOPT_FOLLOWLOCATION, $follow_location); - } - - /** - * Set forbid reuse - * - * @access public - */ - public function setForbidReuse($forbid_reuse = true) - { - $this->setOpt(CURLOPT_FORBID_REUSE, $forbid_reuse); - } - - /** - * Set maximum redirects - * - * @access public - */ - public function setMaximumRedirects($maximum_redirects) - { - $this->setOpt(CURLOPT_MAXREDIRS, $maximum_redirects); + $this->initialize(); } public function getCurl() @@ -1686,6 +1566,16 @@ public function getUrl() return $this->url; } + public function getOptions() + { + return $this->options; + } + + public function getUserSetOptions() + { + return $this->userSetOptions; + } + public function getRequestHeaders() { return $this->requestHeaders; @@ -1788,8 +1678,6 @@ public function getXmlDecoder() /** * Destruct - * - * @access public */ public function __destruct() { @@ -1798,72 +1686,217 @@ public function __destruct() public function __get($name) { - $return = null; - if (in_array($name, self::$deferredProperties, true) && is_callable([$this, $getter = '_get_' . $name])) { - $return = $this->$name = $this->$getter(); + if (in_array($name, self::$deferredProperties, true)) { + if (isset($this->deferredValues[$name])) { + return $this->deferredValues[$name]; + } elseif (is_callable([$this, $getter = 'get' . ucfirst($name)])) { + $this->deferredValues[$name] = $this->$getter(); + return $this->deferredValues[$name]; + } + } + + return null; + } + + public function __isset($name) + { + if (in_array($name, self::$deferredProperties, true)) { + if (isset($this->deferredValues[$name])) { + return true; + } elseif (is_callable([$this, $getter = 'get' . ucfirst($name)])) { + $this->deferredValues[$name] = $this->$getter(); + return true; + } else { + return false; + } + } + + return isset($this->$name); + } + + /** + * Get Curl Error Code Constants + */ + private function getCurlErrorCodeConstants() + { + $constants = get_defined_constants(true); + $filtered_array = array_filter( + $constants['curl'], + function ($key) { + return strpos($key, 'CURLE_') !== false; + }, + ARRAY_FILTER_USE_KEY + ); + $curl_const_by_code = array_flip($filtered_array); + return $curl_const_by_code; + } + + /** + * Get Curl Error Code Constant + */ + private function getCurlErrorCodeConstant() + { + $curl_const_by_code = $this->curlErrorCodeConstants ?? []; + if (isset($curl_const_by_code[$this->curlErrorCode])) { + return $curl_const_by_code[$this->curlErrorCode]; } - return $return; + return ''; } /** - * Get Effective Url + * Get Curl Option Code Constants + */ + private function getCurlOptionCodeConstants() + { + $constants = get_defined_constants(true); + $filtered_array = array_filter( + $constants['curl'], + function ($key) { + return strpos($key, 'CURLOPT_') !== false; + }, + ARRAY_FILTER_USE_KEY + ); + $curl_const_by_code = array_flip($filtered_array); + + if (!isset($curl_const_by_code[CURLINFO_HEADER_OUT])) { + $curl_const_by_code[CURLINFO_HEADER_OUT] = 'CURLINFO_HEADER_OUT'; + } + + return $curl_const_by_code; + } + + /** + * Display Curl Option Value. * - * @access private + * @param $option + * @param $value */ - private function _get_effectiveUrl() + public function displayCurlOptionValue($option, $value = null) + { + if ($value === null) { + $value = $this->getOpt($option); + } + + if (isset($this->curlOptionCodeConstants[$option])) { + echo $this->curlOptionCodeConstants[$option] . ':'; + } else { + echo $option . ':'; + } + + if (is_string($value)) { + echo ' "' . $value . '"' . "\n"; + } elseif (is_int($value)) { + echo ' ' . (string)$value; + + $bit_flag_lookups = [ + 'CURLOPT_HTTPAUTH' => 'CURLAUTH_', + 'CURLOPT_PROTOCOLS' => 'CURLPROTO_', + 'CURLOPT_PROXYAUTH' => 'CURLAUTH_', + 'CURLOPT_PROXY_SSL_OPTIONS' => 'CURLSSLOPT_', + 'CURLOPT_REDIR_PROTOCOLS' => 'CURLPROTO_', + 'CURLOPT_SSH_AUTH_TYPES' => 'CURLSSH_AUTH_', + 'CURLOPT_SSL_OPTIONS' => 'CURLSSLOPT_', + ]; + if (isset($this->curlOptionCodeConstants[$option])) { + $option_name = $this->curlOptionCodeConstants[$option]; + if (in_array($option_name, array_keys($bit_flag_lookups), true)) { + $curl_const_prefix = $bit_flag_lookups[$option_name]; + $constants = get_defined_constants(true); + $curl_constants = array_filter( + $constants['curl'], + function ($key) use ($curl_const_prefix) { + return strpos($key, $curl_const_prefix) !== false; + }, + ARRAY_FILTER_USE_KEY + ); + + $bit_flags = []; + foreach ($curl_constants as $const_name => $const_value) { + // Attempt to detect bit flags in use that use constants with negative values (e.g. + // CURLAUTH_ANY, CURLAUTH_ANYSAFE, CURLPROTO_ALL, CURLSSH_AUTH_ANY, + // CURLSSH_AUTH_DEFAULT, etc.) + if ($value < 0 && $value === $const_value) { + $bit_flags[] = $const_name; + break; + } elseif ($value >= 0 && $const_value >= 0 && ($value & $const_value)) { + $bit_flags[] = $const_name; + } + } + + if (count($bit_flags)) { + asort($bit_flags); + echo ' (' . implode(' | ', $bit_flags) . ')'; + } + } + } + + echo "\n"; + } elseif (is_bool($value)) { + echo ' ' . ($value ? 'true' : 'false') . "\n"; + } elseif (is_array($value)) { + echo ' '; + var_dump($value); + } elseif (is_callable($value)) { + echo ' (callable)' . "\n"; + } else { + echo ' ' . gettype($value) . ':' . "\n"; + var_dump($value); + } + } + + /** + * Get Effective Url + */ + private function getEffectiveUrl() { return $this->getInfo(CURLINFO_EFFECTIVE_URL); } /** * Get RFC 2616 - * - * @access private */ - private function _get_rfc2616() + private function getRfc2616() { return array_fill_keys(self::$RFC2616, true); } /** * Get RFC 6265 - * - * @access private */ - private function _get_rfc6265() + private function getRfc6265() { return array_fill_keys(self::$RFC6265, true); } /** * Get Total Time - * - * @access private */ - private function _get_totalTime() + private function getTotalTime() { return $this->getInfo(CURLINFO_TOTAL_TIME); } /** * Build Cookies - * - * @access private */ private function buildCookies() { - // Avoid using http_build_query() as unnecessary encoding is performed. - // http_build_query($this->cookies, '', '; '); - $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) { - return $k . '=' . $v; - }, array_keys($this->cookies), array_values($this->cookies)))); + // Avoid changing CURLOPT_COOKIE if there are no cookies set. + if (count($this->cookies)) { + // Avoid using http_build_query() as unnecessary encoding is performed. + // http_build_query($this->cookies, '', '; '); + $cookies = []; + foreach ($this->cookies as $key => $value) { + $cookies[] = $key . '=' . $value; + } + $this->setOpt(CURLOPT_COOKIE, implode('; ', $cookies)); + } } /** * Download Complete * - * @access private - * @param $fh + * @param $fh */ private function downloadComplete($fh) { @@ -1882,13 +1915,15 @@ private function downloadComplete($fh) // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the // PHP script from stdin. Using null causes "Warning: curl_setopt(): // supplied argument is not a valid File-Handle resource". - if (!defined('STDOUT')) { - define('STDOUT', fopen('php://stdout', 'w')); + if (defined('STDOUT')) { + $output = STDOUT; + } else { + $output = fopen('php://stdout', 'w'); } // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE // resource has gone away, resetting to default". - $this->setFile(STDOUT); + $this->setFile($output); // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent // responses as the return value of curl_exec(). Without this, @@ -1899,20 +1934,21 @@ private function downloadComplete($fh) /** * Parse Headers * - * @access private - * @param $raw_headers - * + * @param $raw_headers * @return array */ private function parseHeaders($raw_headers) { - $raw_headers = preg_split('/\r\n/', (string) $raw_headers, -1, PREG_SPLIT_NO_EMPTY); $http_headers = new CaseInsensitiveArray(); + $raw_headers = preg_split('/\r\n/', (string) $raw_headers, -1, PREG_SPLIT_NO_EMPTY); + if ($raw_headers === false) { + return ['', $http_headers]; + } $raw_headers_count = count($raw_headers); for ($i = 1; $i < $raw_headers_count; $i++) { if (strpos($raw_headers[$i], ':') !== false) { - list($key, $value) = explode(':', $raw_headers[$i], 2); + list($key, $value) = array_pad(explode(':', $raw_headers[$i], 2), 2, ''); $key = trim($key); $value = trim($value); // Use isset() as array_key_exists() and ArrayAccess are not compatible. @@ -1924,15 +1960,13 @@ private function parseHeaders($raw_headers) } } - return [isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers]; + return [$raw_headers['0'] ?? '', $http_headers]; } /** * Parse Request Headers * - * @access private - * @param $raw_headers - * + * @param $raw_headers * @return \Curl\CaseInsensitiveArray */ private function parseRequestHeaders($raw_headers) @@ -1949,19 +1983,21 @@ private function parseRequestHeaders($raw_headers) /** * Parse Response * - * @access private - * @param $response_headers - * @param $raw_response - * + * @param $response_headers + * @param $raw_response * @return mixed - * If the response content-type is json: - * Returns the json decoder's return value: A stdClass object when the default json decoder is used. - * If the response content-type is xml: - * Returns the xml decoder's return value: A SimpleXMLElement object when the default xml decoder is used. - * If the response content-type is something else: - * Returns the original raw response unless a default decoder has been set. - * If the response content-type cannot be determined: - * Returns the original raw response. + * If the response content-type is json: Returns the json decoder's return value: A stdClass object + * when the default json decoder is used. + * + * If the response content-type is xml: Returns the xml decoder's return value: A SimpleXMLElement + * object when the default xml decoder is used. + * + * If the response content-type is something else: Returns the original raw response unless a default + * decoder has been set. + * + * If the response content-type cannot be determined: Returns the original raw response. + * + * If the response content-encoding is gzip: Returns the response gzip-decoded. */ private function parseResponse($response_headers, $raw_response) { @@ -1986,23 +2022,57 @@ private function parseResponse($response_headers, $raw_response) } } + if ( + ( + // Ensure that the server says the response is compressed with + // gzip and the response has not already been decoded. Use + // is_string() to ensure that $response is a string being passed + // to mb_strpos() and gzdecode(). Use extension_loaded() to + // ensure that mb_strpos() uses the mbstring extension and not a + // polyfill. + isset($response_headers['Content-Encoding']) && + $response_headers['Content-Encoding'] === 'gzip' && + is_string($response) && + ( + ( + extension_loaded('mbstring') && + mb_strpos($response, "\x1f" . "\x8b" . "\x08", 0, 'US-ASCII') === 0 + ) || + !extension_loaded('mbstring') + ) + ) || ( + // Or ensure that the response looks like it is compressed with + // gzip. Use is_string() to ensure that $response is a string + // being passed to mb_strpos() and gzdecode(). Use + // extension_loaded() to ensure that mb_strpos() uses the + // mbstring extension and not a polyfill. + is_string($response) && + extension_loaded('mbstring') && + mb_strpos($response, "\x1f" . "\x8b" . "\x08", 0, 'US-ASCII') === 0 + ) + ) { + // Use @ to suppress message "Warning gzdecode(): data error". + $decoded_response = @gzdecode($response); + if ($decoded_response !== false) { + $response = $decoded_response; + } + } + return $response; } /** * Parse Response Headers * - * @access private - * @param $raw_response_headers - * + * @param $raw_response_headers * @return \Curl\CaseInsensitiveArray */ private function parseResponseHeaders($raw_response_headers) { $response_header_array = explode("\r\n\r\n", $raw_response_headers); - $response_header = ''; + $response_header = ''; for ($i = count($response_header_array) - 1; $i >= 0; $i--) { - if (stripos($response_header_array[$i], 'HTTP/') === 0) { + if (isset($response_header_array[$i]) && stripos($response_header_array[$i], 'HTTP/') === 0) { $response_header = $response_header_array[$i]; break; } @@ -2020,9 +2090,8 @@ private function parseResponseHeaders($raw_response_headers) /** * Set Encoded Cookie * - * @access private - * @param $key - * @param $value + * @param $key + * @param $value */ private function setEncodedCookie($key, $value) { @@ -2050,15 +2119,33 @@ private function setEncodedCookie($key, $value) /** * Initialize * - * @access private - * @param $base_url + * @param $base_url + * @param mixed $options */ - private function initialize($base_url = null) + private function initialize($base_url = null, $options = []) { - $this->id = uniqid('', true); - $this->setDefaultUserAgent(); - $this->setDefaultTimeout(); - $this->setOpt(CURLINFO_HEADER_OUT, true); + $this->setProtocolsInternal(CURLPROTO_HTTPS | CURLPROTO_HTTP); + $this->setRedirectProtocolsInternal(CURLPROTO_HTTPS | CURLPROTO_HTTP); + + if (isset($options)) { + $this->setOpts($options); + } + + $this->id = bin2hex(random_bytes(16)); + + // Only set default user agent if not already set. + if (!array_key_exists(CURLOPT_USERAGENT, $this->options)) { + $this->setDefaultUserAgentInternal(); + } + + // Only set default timeout if not already set. + if (!array_key_exists(CURLOPT_TIMEOUT, $this->options)) { + $this->setDefaultTimeoutInternal(); + } + + if (!array_key_exists(CURLINFO_HEADER_OUT, $this->options)) { + $this->setDefaultHeaderOutInternal(); + } // Create a placeholder to temporarily store the header callback data. $header_callback_data = new \stdClass(); @@ -2067,9 +2154,10 @@ private function initialize($base_url = null) $header_callback_data->stopRequestDecider = null; $header_callback_data->stopRequest = false; $this->headerCallbackData = $header_callback_data; - $this->setOpt(CURLOPT_HEADERFUNCTION, createHeaderCallback($header_callback_data)); + $this->setStopInternal(); + $this->setOptInternal(CURLOPT_HEADERFUNCTION, createHeaderCallback($header_callback_data)); - $this->setOpt(CURLOPT_RETURNTRANSFER, true); + $this->setOptInternal(CURLOPT_RETURNTRANSFER, true); $this->headers = new CaseInsensitiveArray(); if ($base_url !== null) { @@ -2090,27 +2178,41 @@ private function initialize($base_url = null) * The callable must return a truthy value for the request to be stopped * early. * - * @access public - * @param $callback callable + * The callable may be set to null to avoid calling the stop request decider + * callback and instead just check the value of stopRequest for attempting + * to stop the request as used by Curl::stop(). + * + * @param $callback callable|null */ - public function setStop($callback) + public function setStop($callback = null) { $this->headerCallbackData->stopRequestDecider = $callback; $this->headerCallbackData->stopRequest = false; $header_callback_data = $this->headerCallbackData; - $this->progress(function ( - $resource, - $download_size, - $downloaded, - $upload_size, - $uploaded - ) use ( - $header_callback_data - ) { - // Abort the transfer when the stop request flag has been set by returning a non-zero value. - return $header_callback_data->stopRequest ? 1 : 0; - }); + $this->progress(createStopRequestFunction($header_callback_data)); + } + + private function setStopInternal($callback = null) + { + $this->headerCallbackData->stopRequestDecider = $callback; + $this->headerCallbackData->stopRequest = false; + + $header_callback_data = $this->headerCallbackData; + $this->progressInternal(createStopRequestFunction($header_callback_data)); + } + + /** + * Stop + * + * Attempt to stop request. + * + * Used by MultiCurl::stop() when making multiple parallel requests. + */ + #[\Override] + public function stop() + { + $this->headerCallbackData->stopRequest = true; } } @@ -2121,11 +2223,11 @@ public function setStop($callback) * unset($curl) automatically calls __destruct() as expected. Otherwise, manually calling $curl->close() will be * necessary to prevent a memory leak. * - * @param $header_callback_data - * + * @param $header_callback_data * @return callable */ -function createHeaderCallback($header_callback_data) { +function createHeaderCallback($header_callback_data) +{ return function ($ch, $header) use ($header_callback_data) { if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) { $header_callback_data->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B"); @@ -2142,3 +2244,29 @@ function createHeaderCallback($header_callback_data) { return strlen($header); }; } + +/** + * Create Stop Request Function + * + * Create a function for Curl::progress() that stops a request early when the + * stopRequest flag is on. Keep this function separate from the class to prevent + * a memory leak. + * + * @param $header_callback_data + * @return callable + */ +function createStopRequestFunction($header_callback_data) +{ + return function ( + $resource, + $download_size, + $downloaded, + $upload_size, + $uploaded + ) use ( + $header_callback_data + ) { + // Abort the transfer when the stop request flag has been set by returning a non-zero value. + return $header_callback_data->stopRequest ? 1 : 0; + }; +} diff --git a/src/Curl/Decoder.php b/src/Curl/Decoder.php index 603f4c3876..b2aa0417db 100644 --- a/src/Curl/Decoder.php +++ b/src/Curl/Decoder.php @@ -1,4 +1,6 @@ -baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url, $query_parameters); $curl->setUrl($url, $query_parameters); @@ -91,15 +82,13 @@ public function addDelete($url, $query_parameters = [], $data = []) /** * Add Download * - * @access public - * @param $url - * @param $mixed_filename - * + * @param $url + * @param $mixed_filename * @return object */ public function addDownload($url, $mixed_filename) { - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url); $curl->setUrl($url); @@ -122,7 +111,7 @@ public function addDownload($url, $mixed_filename) // Attempt to resume download only when a temporary download file exists and is not empty. if (is_file($download_filename) && $filesize = filesize($download_filename)) { $first_byte_position = $filesize; - $range = $first_byte_position . '-'; + $range = (string)$first_byte_position . '-'; $curl->setRange($range); $curl->fileHandle = fopen($download_filename, 'ab'); @@ -138,7 +127,10 @@ public function addDownload($url, $mixed_filename) } else { $curl->fileHandle = fopen('php://temp', 'wb'); $curl->downloadCompleteCallback = function ($instance, $fh) use ($filename) { - file_put_contents($filename, stream_get_contents($fh)); + $contents = stream_get_contents($fh); + if ($contents !== false) { + file_put_contents($filename, $contents); + } }; } } @@ -152,10 +144,8 @@ public function addDownload($url, $mixed_filename) /** * Add Get * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addGet($url, $data = []) @@ -165,7 +155,7 @@ public function addGet($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url, $data); $curl->setUrl($url, $data); @@ -177,10 +167,8 @@ public function addGet($url, $data = []) /** * Add Head * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addHead($url, $data = []) @@ -190,7 +178,7 @@ public function addHead($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url, $data); $curl->setUrl($url, $data); @@ -202,10 +190,8 @@ public function addHead($url, $data = []) /** * Add Options * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addOptions($url, $data = []) @@ -215,7 +201,7 @@ public function addOptions($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url, $data); $curl->setUrl($url, $data); @@ -227,10 +213,8 @@ public function addOptions($url, $data = []) /** * Add Patch * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addPatch($url, $data = []) @@ -240,7 +224,7 @@ public function addPatch($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); if (is_array($data) && empty($data)) { $curl->removeHeader('Content-Length'); @@ -257,13 +241,12 @@ public function addPatch($url, $data = []) /** * Add Post * - * @access public - * @param $url - * @param $data - * @param $follow_303_with_post - * If true, will cause 303 redirections to be followed using a POST request (default: false). - * Note: Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. - * + * @param $url + * @param $data + * @param $follow_303_with_post + * If true, will cause 303 redirections to be followed using a POST request + * (default: false). Note: Redirections are only followed if the + * CURLOPT_FOLLOWLOCATION option is set to true. * @return object */ public function addPost($url, $data = '', $follow_303_with_post = false) @@ -274,7 +257,7 @@ public function addPost($url, $data = '', $follow_303_with_post = false) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url); @@ -300,10 +283,8 @@ public function addPost($url, $data = '', $follow_303_with_post = false) /** * Add Put * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addPut($url, $data = []) @@ -313,7 +294,7 @@ public function addPut($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url); $curl->setUrl($url); @@ -329,10 +310,8 @@ public function addPut($url, $data = []) /** * Add Search * - * @access public - * @param $url - * @param $data - * + * @param $url + * @param $data * @return object */ public function addSearch($url, $data = []) @@ -342,7 +321,7 @@ public function addSearch($url, $data = []) $url = $this->baseUrl; } - $curl = new Curl(http://github.com/$this-%3EbaseUrl); + $curl = new Curl(http://github.com/$this-%3EbaseUrl,%20$this-%3Eoptions); $this->queueHandle($curl); $this->setUrl($url); $curl->setUrl($url); @@ -360,9 +339,7 @@ public function addSearch($url, $data = []) * * Add a Curl instance to the handle queue. * - * @access public - * @param $curl - * + * @param $curl * @return object */ public function addCurl(http://github.com/Curl%20$curl) @@ -371,25 +348,13 @@ public function addCurl(http://github.com/Curl%20$curl) return $curl; } - /** - * Before Send - * - * @access public - * @param $callback callable|null - */ - public function beforeSend($callback) - { - $this->beforeSendCallback = $callback; - } - /** * Close - * - * @access public */ + #[\Override] public function close() { - foreach ($this->curls as $curl) { + foreach ($this->queuedCurls as $curl) { $curl->close(); } @@ -399,85 +364,23 @@ public function close() $this->multiCurl = null; } - /** - * Complete - * - * @access public - * @param $callback callable|null - */ - public function complete($callback) - { - $this->completeCallback = $callback; - } - - /** - * Error - * - * @access public - * @param $callback callable|null - */ - public function error($callback) - { - $this->errorCallback = $callback; - } - - /** - * Get Opt - * - * @access public - * @param $option - * - * @return mixed - */ - public function getOpt($option) - { - return isset($this->options[$option]) ? $this->options[$option] : null; - } - - /** - * Set Basic Authentication - * - * @access public - * @param $username - * @param $password - */ - public function setBasicAuthentication($username, $password = '') - { - $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); - } - /** * Set Concurrency * - * @access public - * @param $concurrency + * @param $concurrency */ public function setConcurrency($concurrency) { $this->concurrency = $concurrency; } - /** - * Set Digest Authentication - * - * @access public - * @param $username - * @param $password - */ - public function setDigestAuthentication($username, $password = '') - { - $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); - } - /** * Set Cookie * - * @access public - * @param $key - * @param $value + * @param $key + * @param $value */ + #[\Override] public function setCookie($key, $value) { $this->cookies[$key] = $value; @@ -486,9 +389,9 @@ public function setCookie($key, $value) /** * Set Cookies * - * @access public - * @param $cookies + * @param $cookies */ + #[\Override] public function setCookies($cookies) { foreach ($cookies as $key => $value) { @@ -496,34 +399,12 @@ public function setCookies($cookies) } } - /** - * Set Port - * - * @access public - * @param $port - */ - public function setPort($port) - { - $this->setOpt(CURLOPT_PORT, (int) $port); - } - - /** - * Set Connect Timeout - * - * @access public - * @param $seconds - */ - public function setConnectTimeout($seconds) - { - $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); - } - /** * Set Cookie String * - * @access public - * @param $string + * @param $string */ + #[\Override] public function setCookieString($string) { $this->setOpt(CURLOPT_COOKIE, $string); @@ -532,9 +413,9 @@ public function setCookieString($string) /** * Set Cookie File * - * @access public - * @param $cookie_file + * @param $cookie_file */ + #[\Override] public function setCookieFile($cookie_file) { $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); @@ -543,34 +424,23 @@ public function setCookieFile($cookie_file) /** * Set Cookie Jar * - * @access public - * @param $cookie_jar + * @param $cookie_jar */ + #[\Override] public function setCookieJar($cookie_jar) { $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); } - /** - * Set File - * - * @access public - * @param $file - */ - public function setFile($file) - { - $this->setOpt(CURLOPT_FILE, $file); - } - /** * Set Header * * Add extra header to include in the request. * - * @access public - * @param $key - * @param $value + * @param $key + * @param $value */ + #[\Override] public function setHeader($key, $value) { $this->headers[$key] = $value; @@ -582,9 +452,9 @@ public function setHeader($key, $value) * * Add extra headers to include in the request. * - * @access public - * @param $headers + * @param $headers */ + #[\Override] public function setHeaders($headers) { if (ArrayUtil::isArrayAssoc($headers)) { @@ -595,7 +465,7 @@ public function setHeaders($headers) } } else { foreach ($headers as $header) { - list($key, $value) = explode(':', $header, 2); + list($key, $value) = array_pad(explode(':', $header, 2), 2, ''); $key = trim($key); $value = trim($value); $this->headers[$key] = $value; @@ -608,9 +478,9 @@ public function setHeaders($headers) /** * Set JSON Decoder * - * @access public - * @param $mixed boolean|callable + * @param $mixed boolean|callable */ + #[\Override] public function setJsonDecoder($mixed) { if ($mixed === false) { @@ -623,9 +493,9 @@ public function setJsonDecoder($mixed) /** * Set XML Decoder * - * @access public - * @param $mixed boolean|callable + * @param $mixed boolean|callable */ + #[\Override] public function setXmlDecoder($mixed) { if ($mixed === false) { @@ -635,101 +505,27 @@ public function setXmlDecoder($mixed) } } - /** - * Set Proxy - * - * Set an HTTP proxy to tunnel requests through. - * - * @access public - * @param $proxy - The HTTP proxy to tunnel requests through. May include port number. - * @param $port - The port number of the proxy to connect to. This port number can also be set in $proxy. - * @param $username - The username to use for the connection to the proxy. - * @param $password - The password to use for the connection to the proxy. - */ - public function setProxy($proxy, $port = null, $username = null, $password = null) - { - $this->setOpt(CURLOPT_PROXY, $proxy); - if ($port !== null) { - $this->setOpt(CURLOPT_PROXYPORT, $port); - } - if ($username !== null && $password !== null) { - $this->setOpt(CURLOPT_PROXYUSERPWD, $username . ':' . $password); - } - } - /** * Set Proxies * * Set proxies to tunnel requests through. When set, a random proxy will be * used for the request. * - * @access public - * @param $proxies array - A list of HTTP proxies to tunnel requests - * through. May include port number. + * @param $proxies array - A list of HTTP proxies to tunnel requests + * through. May include port number. */ public function setProxies($proxies) { $this->proxies = $proxies; } - /** - * Set Proxy Auth - * - * Set the HTTP authentication method(s) to use for the proxy connection. - * - * @access public - * @param $auth - */ - public function setProxyAuth($auth) - { - $this->setOpt(CURLOPT_PROXYAUTH, $auth); - } - - /** - * Set Proxy Type - * - * Set the proxy protocol type. - * - * @access public - * @param $type - */ - public function setProxyType($type) - { - $this->setOpt(CURLOPT_PROXYTYPE, $type); - } - - /** - * Set Proxy Tunnel - * - * Set the proxy to tunnel through HTTP proxy. - * - * @access public - * @param $tunnel boolean - */ - public function setProxyTunnel($tunnel = true) - { - $this->setOpt(CURLOPT_HTTPPROXYTUNNEL, $tunnel); - } - - /** - * Unset Proxy - * - * Disable use of the proxy. - * - * @access public - */ - public function unsetProxy() - { - $this->setOpt(CURLOPT_PROXY, null); - } - /** * Set Opt * - * @access public - * @param $option - * @param $value + * @param $option + * @param $value */ + #[\Override] public function setOpt($option, $value) { $this->options[$option] = $value; @@ -738,9 +534,11 @@ public function setOpt($option, $value) // existing instances when they have not already been set to avoid // unexpectedly changing the request url after is has been specified. if ($option === CURLOPT_URL) { - foreach ($this->curls as $curl_id => $curl) { - if (!isset($this->instanceSpecificOptions[$curl_id][$option]) || - $this->instanceSpecificOptions[$curl_id][$option] === null) { + foreach ($this->queuedCurls as $curl_id => $curl) { + if ( + !isset($this->instanceSpecificOptions[$curl_id][$option]) || + $this->instanceSpecificOptions[$curl_id][$option] === null + ) { $this->instanceSpecificOptions[$curl_id][$option] = $value; } } @@ -750,9 +548,9 @@ public function setOpt($option, $value) /** * Set Opts * - * @access public - * @param $options + * @param $options */ + #[\Override] public function setOpts($options) { foreach ($options as $option => $value) { @@ -760,22 +558,10 @@ public function setOpts($options) } } - /** - * Set Range - * - * @access public - * @param $range - */ - public function setRange($range) - { - $this->setOpt(CURLOPT_RANGE, $range); - } - /** * Set Rate Limit * - * @access public - * @param $rate_limit string (e.g. "60/1m"). + * @param $rate_limit string (e.g. "60/1m"). * @throws \UnexpectedValueException */ public function setRateLimit($rate_limit) @@ -793,12 +579,12 @@ public function setRateLimit($rate_limit) ''; if (!preg_match($rate_limit_pattern, $rate_limit, $matches)) { throw new \UnexpectedValueException( - 'rate limit must be formatted as $max_requests/$interval(s|m|h) ' . + 'rate limit must be formatted as $max_requests_per_interval/$interval(s|m|h) ' . '(e.g. "60/1m" for a maximum of 60 requests per 1 minute)' ); } - $max_requests = (int)$matches['1']; + $max_requests_per_interval = (int)$matches['1']; if ($matches['2'] === '') { $interval = 1; } else { @@ -807,6 +593,7 @@ public function setRateLimit($rate_limit) $unit = strtolower($matches['3']); // Convert interval to seconds based on unit. + $interval_seconds = ''; if ($unit === 's') { $interval_seconds = $interval * 1; } elseif ($unit === 'm') { @@ -815,36 +602,14 @@ public function setRateLimit($rate_limit) $interval_seconds = $interval * 3600; } - $this->rateLimit = $max_requests . '/' . $interval . $unit; + $this->rateLimit = (string)$max_requests_per_interval . '/' . (string)$interval . $unit; $this->rateLimitEnabled = true; - $this->maxRequests = $max_requests; + $this->maxRequestsPerInterval = $max_requests_per_interval; $this->interval = $interval; $this->intervalSeconds = $interval_seconds; $this->unit = $unit; } - /** - * Set Referer - * - * @access public - * @param $referer - */ - public function setReferer($referer) - { - $this->setReferrer($referer); - } - - /** - * Set Referrer - * - * @access public - * @param $referrer - */ - public function setReferrer($referrer) - { - $this->setOpt(CURLOPT_REFERER, $referrer); - } - /** * Set Retry * @@ -856,42 +621,21 @@ public function setReferrer($referrer) * When using a callable decider, the request will be retried until the * function returns a value which evaluates to false. * - * @access public - * @param $mixed + * @param $mixed */ + #[\Override] public function setRetry($mixed) { $this->retry = $mixed; } - /** - * Set Timeout - * - * @access public - * @param $seconds - */ - public function setTimeout($seconds) - { - $this->setOpt(CURLOPT_TIMEOUT, $seconds); - } - - /** - * Disable Timeout - * - * @access public - */ - public function disableTimeout() - { - $this->setTimeout(null); - } - /** * Set Url * - * @access public - * @param $url - * @param $mixed_data + * @param $url + * @param $mixed_data */ + #[\Override] public function setUrl($url, $mixed_data = '') { $built_url = Url::buildUrl($url, $mixed_data); @@ -905,35 +649,9 @@ public function setUrl($url, $mixed_data = '') $this->setOpt(CURLOPT_URL, $this->baseUrl); } - /** - * Set User Agent - * - * @access public - * @param $user_agent - */ - public function setUserAgent($user_agent) - { - $this->setOpt(CURLOPT_USERAGENT, $user_agent); - } - - /** - * Set Interface - * - * The name of the outgoing network interface to use. - * This can be an interface name, an IP address or a host name. - * - * @access public - * @param $interface - */ - public function setInterface($interface) - { - $this->setOpt(CURLOPT_INTERFACE, $interface); - } - /** * Start * - * @access public * @throws \ErrorException */ public function start() @@ -948,7 +666,8 @@ public function start() $this->currentRequestCount = 0; do { - while (count($this->curls) && + while ( + count($this->queuedCurls) && count($this->activeCurls) < $this->concurrency && (!$this->rateLimitEnabled || $this->hasRequestQuota()) ) { @@ -959,19 +678,45 @@ public function start() $this->waitUntilRequestQuotaAvailable(); } - // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly block. - // https://bugs.php.net/bug.php?id=63411 - // - // Also, use a shorter curl_multi_select() timeout instead the default of one second. This allows pending - // requests to have more accurate start times. Without a shorter timeout, it can be nearly a full second - // before available request quota is rechecked and pending requests can be initialized. - if (curl_multi_select($this->multiCurl, 0.2) === -1) { - usleep(100000); - } + if ($this->preferRequestTimeAccuracy) { + // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly + // block. + // https://bugs.php.net/bug.php?id=63411 + // + // Also, use a shorter curl_multi_select() timeout instead the default of one second. This allows + // pending requests to have more accurate start times. Without a shorter timeout, it can be nearly a + // full second before available request quota is rechecked and pending requests can be initialized. + if (curl_multi_select($this->multiCurl, 0.2) === -1) { + usleep(100000); + } - curl_multi_exec($this->multiCurl, $active); + curl_multi_exec($this->multiCurl, $active); + } else { + // Use multiple loops to get data off of the multi handler. Without this, the following error may appear + // intermittently on certain versions of PHP: + // curl_multi_exec(): supplied resource is not a valid cURL handle resource + + // Clear out the curl buffer. + do { + $status = curl_multi_exec($this->multiCurl, $active); + } while ($status === CURLM_CALL_MULTI_PERFORM); + + // Wait for more information and then get that information. + while ($active && $status === CURLM_OK) { + // Check if the network socket has some data. + if (curl_multi_select($this->multiCurl) !== -1) { + // Process the data for as long as the system tells us to keep getting it. + do { + $status = curl_multi_exec($this->multiCurl, $active); + } while ($status === CURLM_CALL_MULTI_PERFORM); + } + } + } - while (($info_array = curl_multi_info_read($this->multiCurl)) !== false) { + while ( + (is_resource($this->multiCurl) || $this->multiCurl instanceof \CurlMultiHandle) && + (($info_array = curl_multi_info_read($this->multiCurl)) !== false) + ) { if ($info_array['msg'] === CURLMSG_DONE) { foreach ($this->activeCurls as $key => $curl) { if ($curl->curl === $info_array['handle']) { @@ -991,6 +736,8 @@ public function start() 'cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code) ); } + + $curl->call($curl->beforeSendCallback); } else { $curl->execDone(); @@ -1009,122 +756,59 @@ public function start() } } } - } while ($active || count($this->activeCurls) || count($this->curls)); + } while ($active || count($this->activeCurls) || count($this->queuedCurls)); $this->isStarted = false; $this->stopTime = microtime(true); } /** - * Success - * - * @access public - * @param $callback callable|null + * Stop */ - public function success($callback) + #[\Override] + public function stop() { - $this->successCallback = $callback; - } + // Remove any queued curl requests. + while (count($this->queuedCurls)) { + $curl = array_pop($this->queuedCurls); + $curl->close(); + } - /** - * Unset Header - * - * Remove extra header previously set using Curl::setHeader(). - * - * @access public - * @param $key - */ - public function unsetHeader($key) - { - unset($this->headers[$key]); - } + // Attempt to stop active curl requests. + while (count($this->activeCurls)) { + // Remove instance from active curls. + $curl = array_pop($this->activeCurls); - /** - * Remove Header - * - * Remove an internal header from the request. - * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. - * - * @access public - * @param $key - */ - public function removeHeader($key) - { - $this->setHeader($key, ''); - } + // Remove active curl handle. + curl_multi_remove_handle($this->multiCurl, $curl->curl); - /** - * Verbose - * - * @access public - * @param bool $on - * @param resource $output - */ - public function verbose($on = true, $output = STDERR) - { - // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side - // effect of causing Curl::requestHeaders to be empty. - if ($on) { - $this->setOpt(CURLINFO_HEADER_OUT, false); + $curl->stop(); } - $this->setOpt(CURLOPT_VERBOSE, $on); - $this->setOpt(CURLOPT_STDERR, $output); - } - - /** - * Set auto referer - * - * @access public - */ - public function setAutoReferer($auto_referer = true) - { - $this->setAutoReferrer($auto_referer); - } - - /** - * Set auto referrer - * - * @access public - */ - public function setAutoReferrer($auto_referrer = true) - { - $this->setOpt(CURLOPT_AUTOREFERER, $auto_referrer); } /** - * Set follow location + * Unset Header * - * @access public - */ - public function setFollowLocation($follow_location = true) - { - $this->setOpt(CURLOPT_FOLLOWLOCATION, $follow_location); - } - - /** - * Set forbid reuse + * Remove extra header previously set using Curl::setHeader(). * - * @access public + * @param $key */ - public function setForbidReuse($forbid_reuse = true) + #[\Override] + public function unsetHeader($key) { - $this->setOpt(CURLOPT_FORBID_REUSE, $forbid_reuse); + unset($this->headers[$key]); } /** - * Set maximum redirects - * - * @access public + * Set request time accuracy */ - public function setMaximumRedirects($maximum_redirects) + public function setRequestTimeAccuracy() { - $this->setOpt(CURLOPT_MAXREDIRS, $maximum_redirects); + $this->preferRequestTimeAccuracy = true; } /** * Destruct - * - * @access public */ public function __destruct() { @@ -1133,12 +817,10 @@ public function __destruct() /** * Update Headers - * - * @access private */ private function updateHeaders() { - foreach ($this->curls as $curl) { + foreach ($this->queuedCurls as $curl) { $curl->setHeaders($this->headers); } } @@ -1146,29 +828,30 @@ private function updateHeaders() /** * Queue Handle * - * @access private - * @param $curl + * @param $curl */ private function queueHandle($curl) { // Use sequential ids to allow for ordered post processing. $curl->id = $this->nextCurlId++; $curl->childOfMultiCurl = true; - $this->curls[$curl->id] = $curl; + $this->queuedCurls[$curl->id] = $curl; - $curl->setHeaders($this->headers); + // Avoid overwriting any existing header. + if ($curl->getOpt(CURLOPT_HTTPHEADER) === null) { + $curl->setHeaders($this->headers); + } } /** * Init Handle * - * @access private - * @param $curl + * @param $curl * @throws \ErrorException */ private function initHandle() { - $curl = array_shift($this->curls); + $curl = array_shift($this->queuedCurls); if ($curl === null) { return; } @@ -1181,6 +864,9 @@ private function initHandle() if ($curl->beforeSendCallback === null) { $curl->beforeSend($this->beforeSendCallback); } + if ($curl->afterSendCallback === null) { + $curl->afterSend($this->afterSendCallback); + } if ($curl->successCallback === null) { $curl->success($this->successCallback); } @@ -1199,9 +885,6 @@ private function initHandle() $curl->setXmlDecoder($this->xmlDecoder); } - // Pass options set on the MultiCurl instance to the Curl instance. - $curl->setOpts($this->options); - // Set instance-specific options on the Curl instance when present. if (isset($this->instanceSpecificOptions[$curl->id])) { $curl->setOpts($this->instanceSpecificOptions[$curl->id]); @@ -1230,47 +913,48 @@ private function initHandle() * * Checks if there is any available quota to make additional requests while * rate limiting is enabled. - * - * @access private */ private function hasRequestQuota() { // Calculate if there's request quota since ratelimiting is enabled. if ($this->rateLimitEnabled) { // Determine if the limit of requests per interval has been reached. - if ($this->currentRequestCount >= $this->maxRequests) { + if ($this->currentRequestCount >= $this->maxRequestsPerInterval) { $micro_time = microtime(true); $elapsed_seconds = $micro_time - $this->currentStartTime; if ($elapsed_seconds <= $this->intervalSeconds) { - $this->rateLimitReached = true; + // Rate limit reached. return false; - } elseif ($this->rateLimitReached) { - $this->rateLimitReached = false; + } else { + // Rate limit not reached. Rate limit interval has passed, + // reset counters. $this->currentStartTime = $micro_time; $this->currentRequestCount = 0; } } - - return true; - } else { - return true; } + + return true; } /** * Wait Until Request Quota Available * * Waits until there is available request quota available based on the rate limit. - * - * @access private */ private function waitUntilRequestQuotaAvailable() { - $sleep_until = $this->currentStartTime + $this->intervalSeconds; + $sleep_until = (float)($this->currentStartTime + $this->intervalSeconds); $sleep_seconds = $sleep_until - microtime(true); // Avoid using time_sleep_until() as it appears to be less precise and not sleep long enough. - usleep((int) $sleep_seconds * 1000000); + // Avoid using usleep(): "Values larger than 1000000 (i.e. sleeping for + // more than a second) may not be supported by the operating system. + // Use sleep() instead." + $sleep_seconds_int = (int)$sleep_seconds; + if ($sleep_seconds_int >= 1) { + sleep($sleep_seconds_int); + } // Ensure that enough time has passed as usleep() may not have waited long enough. $this->currentStartTime = microtime(true); @@ -1283,4 +967,9 @@ private function waitUntilRequestQuotaAvailable() $this->currentRequestCount = 0; } + + public function getActiveCurls() + { + return $this->activeCurls; + } } diff --git a/src/Curl/StringUtil.php b/src/Curl/StringUtil.php index 86341f9bbf..cc7c68be55 100644 --- a/src/Curl/StringUtil.php +++ b/src/Curl/StringUtil.php @@ -1,4 +1,6 @@ -relativeUrl = $relative_url; } - public function __toString() + public function __toString(): string { return $this->absolutizeUrl(); } @@ -24,6 +24,8 @@ public function __toString() * Remove dot segments. * * Interpret and remove the special "." and ".." path segments from a referenced path. + * + * @param mixed $input */ public static function removeDotSegments($input) { @@ -87,10 +89,8 @@ public static function removeDotSegments($input) /** * Build Url * - * @access public - * @param $url - * @param $mixed_data - * + * @param $url + * @param $mixed_data * @return string */ public static function buildUrl($url, $mixed_data = '') @@ -127,29 +127,29 @@ private function absolutizeUrl() $target = []; if (isset($r['scheme'])) { $target['scheme'] = $r['scheme']; - $target['host'] = isset($r['host']) ? $r['host'] : null; - $target['port'] = isset($r['port']) ? $r['port'] : null; - $target['user'] = isset($r['user']) ? $r['user'] : null; - $target['pass'] = isset($r['pass']) ? $r['pass'] : null; + $target['host'] = $r['host'] ?? null; + $target['port'] = $r['port'] ?? null; + $target['user'] = $r['user'] ?? null; + $target['pass'] = $r['pass'] ?? null; $target['path'] = isset($r['path']) ? self::removeDotSegments($r['path']) : null; - $target['query'] = isset($r['query']) ? $r['query'] : null; + $target['query'] = $r['query'] ?? null; } else { - $target['scheme'] = isset($b['scheme']) ? $b['scheme'] : null; + $target['scheme'] = $b['scheme'] ?? null; if ($r['authorized']) { - $target['host'] = isset($r['host']) ? $r['host'] : null; - $target['port'] = isset($r['port']) ? $r['port'] : null; - $target['user'] = isset($r['user']) ? $r['user'] : null; - $target['pass'] = isset($r['pass']) ? $r['pass'] : null; + $target['host'] = $r['host'] ?? null; + $target['port'] = $r['port'] ?? null; + $target['user'] = $r['user'] ?? null; + $target['pass'] = $r['pass'] ?? null; $target['path'] = isset($r['path']) ? self::removeDotSegments($r['path']) : null; - $target['query'] = isset($r['query']) ? $r['query'] : null; + $target['query'] = $r['query'] ?? null; } else { - $target['host'] = isset($b['host']) ? $b['host'] : null; - $target['port'] = isset($b['port']) ? $b['port'] : null; - $target['user'] = isset($b['user']) ? $b['user'] : null; - $target['pass'] = isset($b['pass']) ? $b['pass'] : null; + $target['host'] = $b['host'] ?? null; + $target['port'] = $b['port'] ?? null; + $target['user'] = $b['user'] ?? null; + $target['pass'] = $b['pass'] ?? null; if (!isset($r['path']) || $r['path'] === '') { $target['path'] = $b['path']; - $target['query'] = isset($r['query']) ? $r['query'] : (isset($b['query']) ? $b['query'] : null); + $target['query'] = $r['query'] ?? $b['query'] ?? null; } else { if (StringUtil::startsWith($r['path'], '/')) { $target['path'] = self::removeDotSegments($r['path']); @@ -160,14 +160,14 @@ private function absolutizeUrl() } $target['path'] = self::removeDotSegments($base . '/' . $r['path']); } - $target['query'] = isset($r['query']) ? $r['query'] : null; + $target['query'] = $r['query'] ?? null; } } } if ($this->relativeUrl === '') { - $target['fragment'] = isset($b['fragment']) ? $b['fragment'] : null; + $target['fragment'] = $b['fragment'] ?? null; } else { - $target['fragment'] = isset($r['fragment']) ? $r['fragment'] : null; + $target['fragment'] = $r['fragment'] ?? null; } $absolutized_url = $this->unparseUrl($target); return $absolutized_url; @@ -177,6 +177,8 @@ private function absolutizeUrl() * Parse url. * * Parse url into components of a URI as specified by RFC 3986. + * + * @param mixed $url */ public static function parseUrl($url) { @@ -192,6 +194,8 @@ public static function parseUrl($url) * * Percent-encode characters to represent a data octet in a component when * that octet's corresponding character is outside the allowed set. + * + * @param mixed $chars */ private static function percentEncodeChars($chars) { @@ -229,16 +233,18 @@ function ($matches) { * Unparse url. * * Combine url components into a url. + * + * @param mixed $parsed_url */ private function unparseUrl($parsed_url) { $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; - $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + $user = $parsed_url['user'] ?? ''; $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; $pass = ($user || $pass) ? $pass . '@' : ''; - $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + $host = $parsed_url['host'] ?? ''; $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; - $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $path = $parsed_url['path'] ?? ''; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; $unparsed_url = $scheme . $user . $pass . $host . $port . $path . $query . $fragment; diff --git a/tests/.php-cs-fixer.php b/tests/.php-cs-fixer.php new file mode 100644 index 0000000000..b9014c2307 --- /dev/null +++ b/tests/.php-cs-fixer.php @@ -0,0 +1,88 @@ +in(dirname(__DIR__)); + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setRules([ + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'constant_case' => [ + 'case' => 'lower', + ], + 'elseif' => true, + 'encoding' => true, + 'general_phpdoc_annotation_remove' => [ + 'annotations' => [ + 'access', + 'author', + ], + 'case_sensitive' => false, + ], + 'is_null' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_leading_import_slash' => true, + 'no_unused_imports' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + ], + 'phpdoc_add_missing_param_annotation' => [ + 'only_untyped' => false, + ], + 'phpdoc_align' => [ + 'align' => 'vertical', + ], + 'phpdoc_indent' => true, + 'phpdoc_line_span' => [ + 'const' => 'multi', + 'method' => 'multi', + 'property' => 'multi', + ], + 'phpdoc_order' => [ + 'order' => [ + 'param', + 'return', + 'throws', + 'see', + ], + ], + 'phpdoc_param_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => [ + 'groups' => [ + [ + 'deprecated', + 'expectedException', + 'param', + 'requires', + 'return', + 'see', + 'throws', + ], + ], + 'skip_unlisted_annotations' => false, + ], + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'alpha', + ], + 'short_scalar_cast' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => true, + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], + ]) + ->setFinder($finder); + +return $config; diff --git a/tests/.pre-commit-config.yaml b/tests/.pre-commit-config.yaml new file mode 100644 index 0000000000..b0d2728d60 --- /dev/null +++ b/tests/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +# Pre-commit git hooks. +# +# Usage: +# 1. Run one of: +# $ pip install pre-commit +# $ brew install pre-commit +# +# 2. Install hooks +# $ pre-commit install --config="tests/.pre-commit-config.yaml" +# +# 3. Optionally, enable automatic updates +# $ pre-commit autoupdate --config="tests/.pre-commit-config.yaml" +repos: +- repo: https://github.com/psf/black + rev: 25.11.0 + hooks: + - id: black + name: black + entry: black +- repo: https://github.com/pycqa/flake8 + rev: 7.3.0 + hooks: + - id: flake8 + args: ["--config", "tests/setup.cfg"] +- repo: https://github.com/pycqa/isort + rev: 7.0.0 + hooks: + - id: isort + name: isort + args: ["--force-single-line-imports", "--profile", "black"] +- repo: local + hooks: + - id: composer-validate + name: composer-validate + language: script + entry: scripts/pre-commit.sh + files: 'composer\.json' + pass_filenames: true +- repo: https://github.com/jazzband/pip-tools + rev: v7.5.2 + hooks: + - id: pip-compile + name: pip-compile make_release_requirements.in + files: ^(.*/)?make_release_requirements\.(in|txt)$ + entry: bash -c 'cd scripts/ && pip-compile --output-file="make_release_requirements.txt" "make_release_requirements.in"' diff --git a/tests/PHPCurlClass/ContentRangeServer.php b/tests/ContentRangeServer.php similarity index 97% rename from tests/PHPCurlClass/ContentRangeServer.php rename to tests/ContentRangeServer.php index c691d739f9..5be219a7d0 100644 --- a/tests/PHPCurlClass/ContentRangeServer.php +++ b/tests/ContentRangeServer.php @@ -1,4 +1,6 @@ -testUrl = $port === null ? self::TEST_URL : $this->getTestUrl($port); @@ -119,11 +123,11 @@ function mime_type($file_path) { $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime_type = finfo_file($finfo, $file_path); - finfo_close($finfo); return $mime_type; } -function upload_file_to_server($upload_file_path) { +function upload_file_to_server($upload_file_path) +{ $upload_test = new Test(); $upload_test->server('upload_response', 'POST', [ 'image' => '@' . $upload_file_path, @@ -139,7 +143,8 @@ function upload_file_to_server($upload_file_path) { return $uploaded_file_path; } -function remove_file_from_server($uploaded_file_path) { +function remove_file_from_server($uploaded_file_path) +{ $download_test = new Test(); // Ensure file successfully removed. @@ -189,7 +194,8 @@ function get_request_stats($request_stats, $multi_curl) return $request_stats; } -function get_request_method($instance) { +function get_request_method($instance) +{ $request_method = $instance->getOpt(CURLOPT_CUSTOMREQUEST); // POST requests have CURLOPT_CUSTOMREQUEST unset by default to allow diff --git a/tests/PHPCurlClass/ArrayUtilTest.php b/tests/PHPCurlClass/ArrayUtilTest.php index e501b2d8df..f69bbda987 100644 --- a/tests/PHPCurlClass/ArrayUtilTest.php +++ b/tests/PHPCurlClass/ArrayUtilTest.php @@ -1,4 +1,6 @@ -assertTrue(\Curl\ArrayUtil::isArrayAssoc([ + $this->assertTrue(ArrayUtil::isArrayAssoc([ 'foo' => 'wibble', 'bar' => 'wubble', 'baz' => 'wobble', @@ -18,7 +20,7 @@ public function testArrayAssociative() public function testArrayIndexed() { - $this->assertFalse(\Curl\ArrayUtil::isArrayAssoc([ + $this->assertFalse(ArrayUtil::isArrayAssoc([ 'wibble', 'wubble', 'wobble', @@ -30,4 +32,61 @@ public function testCaseInsensitiveArrayIsArrayAssoc() $array = new CaseInsensitiveArray(); $this->assertTrue(ArrayUtil::isArrayAssoc($array)); } + + public function testArrayFlattenMultidimArray() + { + $data = [ + 'key-1' => 'value-1', + 'key-2' => 'value-2', + 'key-3' => [ + 'nested-key-1' => 'nested-value-1', + 'nested-key-2' => 'nested-value-2', + 'nested-key-3' => [ + 'nested-more-key-1' => 'nested-more-value-1', + 'nested-more-key-2' => 'nested-more-value-2', + ], + ], + ]; + + $this->assertEquals([ + 'key-1' => 'value-1', + 'key-2' => 'value-2', + 'key-3[nested-key-1]' => 'nested-value-1', + 'key-3[nested-key-2]' => 'nested-value-2', + 'key-3[nested-key-3][nested-more-key-1]' => 'nested-more-value-1', + 'key-3[nested-key-3][nested-more-key-2]' => 'nested-more-value-2', + ], ArrayUtil::arrayFlattenMultidim($data)); + } + + public function testArrayFlattenMultidimOrdering() + { + $data = [ + 'foo' => 'bar', + 'baz' => [ + 'qux' => [ + ], + 'wibble' => 'wobble', + ], + ]; + + $result = ArrayUtil::arrayFlattenMultidim($data); + + // Avoid using assertEquals() as it isn't strict about ordering: + // $this->assertEquals([ + // 'foo' => 'bar', + // 'baz[qux]' => '', + // 'baz[wibble]' => 'wobble', + // ], ArrayUtil::arrayFlattenMultidim($data)); + + $result_keys = array_keys($result); + $result_values = array_values($result); + + $this->assertEquals('foo', $result_keys[0]); + $this->assertEquals('baz[qux]', $result_keys[1]); + $this->assertEquals('baz[wibble]', $result_keys[2]); + + $this->assertEquals('bar', $result_values[0]); + $this->assertEquals('', $result_values[1]); + $this->assertEquals('wobble', $result_values[2]); + } } diff --git a/tests/PHPCurlClass/PHPCurlClassTest.php b/tests/PHPCurlClass/PHPCurlClassTest.php index ddace8f179..4991d646ba 100644 --- a/tests/PHPCurlClass/PHPCurlClassTest.php +++ b/tests/PHPCurlClass/PHPCurlClassTest.php @@ -1,4 +1,6 @@ -skip_slow_tests = in_array(getenv('PHP_CURL_CLASS_SKIP_SLOW_TESTS'), ['1', 'y', 'Y']); + $this->skip_slow_tests = in_array(getenv('PHP_CURL_CLASS_SKIP_SLOW_TESTS'), ['1', 'y', 'Y'], true); } public function testExtensionsLoaded() @@ -72,6 +74,24 @@ public function testBuildPostDataArgSeparator() } } + public function testBuildPostDataIntegerKey() + { + $curl = new Curl(); + $this->assertEquals('abc=foo&123=bar', $curl->buildPostData([ + 'abc' => 'foo', + '123' => 'bar', + ])); + } + + public function testBuildPostDataObject() + { + $data = new \stdClass(); + $data->{'abc'} = 'foo'; + $data->{'123'} = 'bar'; + $curl = new Curl(); + $this->assertEquals('abc=foo&123=bar', $curl->buildPostData($data)); + } + public function testUserAgent() { $php_version = preg_replace('/([\.\+\?\*\(\)\[\]\^\$\/])/', '\\\\\1', 'PHP/' . PHP_VERSION); @@ -80,8 +100,8 @@ public function testUserAgent() $test = new Test(); $user_agent = $test->server('server', 'GET', ['key' => 'HTTP_USER_AGENT']); - $this->assertRegExp('/' . $php_version . '/', $user_agent); - $this->assertRegExp('/' . $curl_version . '/', $user_agent); + $this->assertMatchesRegularExpression('/' . $php_version . '/', $user_agent); + $this->assertMatchesRegularExpression('/' . $curl_version . '/', $user_agent); } public function testGet() @@ -390,6 +410,14 @@ public function testPostMultidimensionalDataWithFile() ]; } + if (class_exists('CURLStringFile')) { + $file_path_3 = \Helper\get_png(); + $tests[] = [ + 'file_path' => $file_path_3, + 'post_data_image' => new \CURLStringFile(file_get_contents($file_path_3), 'image', 'image/png'), + ]; + } + foreach ($tests as $test_data) { $file_path = $test_data['file_path']; $post_data_image = $test_data['post_data_image']; @@ -450,18 +478,38 @@ public function testPostFilePathUpload() public function testPostCurlFileUpload() { - if (class_exists('CURLFile')) { - $file_path = \Helper\get_png(); + if (!class_exists('CURLFile')) { + $this->markTestSkipped(); + } - $test = new Test(); - $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ - 'key' => 'image', - 'image' => new \CURLFile($file_path), - ])); + $file_path = \Helper\get_png(); - unlink($file_path); - $this->assertFalse(file_exists($file_path)); + $test = new Test(); + $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ + 'key' => 'image', + 'image' => new \CURLFile($file_path), + ])); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); + } + + public function testPostCurlStringFileUpload() + { + if (!class_exists('CURLStringFile')) { + $this->markTestSkipped(); } + + $file_path = \Helper\get_png(); + + $test = new Test(); + $this->assertEquals('image/png', $test->server('post_file_path_upload', 'POST', [ + 'key' => 'image', + 'image' => new \CURLStringFile(file_get_contents($file_path), 'image', 'image/png'), + ])); + + unlink($file_path); + $this->assertFalse(file_exists($file_path)); } public function testPostNonFilePathUpload() @@ -705,7 +753,8 @@ public function testDownloadRange() $filesize = filesize($filename); - foreach ([ + foreach ( + [ false, 0, 1, @@ -736,7 +785,8 @@ public function testDownloadRange() $filesize + 2, $filesize + 3, - ] as $length) { + ] as $length + ) { $source = Test::TEST_URL; $destination = \Helper\get_tmp_file_path(); @@ -838,6 +888,81 @@ public function testDownloadCallbackError() $this->assertFalse($download_callback_called); } + public function testFastDownloadSuccessOnly() + { + // Create a local file. + $local_file_path = \Helper\get_png(); + + // Upload file to server. + $uploaded_server_file_path = \Helper\upload_file_to_server($local_file_path); + + // Download server file and save locally. + $url = Test::TEST_URL . '?' . http_build_query([ + 'file_path' => $uploaded_server_file_path, + ]); + $downloaded_local_file_path = \Helper\get_tmp_file_path(); + $curl = new Curl(); + $curl->setHeader('X-DEBUG-TEST', 'download_response'); + $curl->fastDownload($url, $downloaded_local_file_path); + + $this->assertEquals(md5_file($local_file_path), md5_file($downloaded_local_file_path)); + + // Remove server file. + \Helper\remove_file_from_server($uploaded_server_file_path); + + unlink($local_file_path); + $this->assertFalse(file_exists($local_file_path)); + + unlink($downloaded_local_file_path); + $this->assertFalse(file_exists($downloaded_local_file_path)); + } + + public function testFastDownloadFailOnly() + { + $url = Test::TEST_URL . '?failures=1'; + $downloaded_local_file_path = \Helper\get_tmp_file_path(); + + $curl = new Curl(); + $curl->setHeader('X-DEBUG-TEST', 'retry'); + $response = $curl->fastDownload($url, $downloaded_local_file_path); + + $this->assertFalse($response); + } + + public function testFastDownloadSuccessFail() + { + $url = Test::TEST_URL . '?failures=0,1'; + $downloaded_local_file_path = \Helper\get_tmp_file_path(); + $cookie_jar = __DIR__ . '/cookiejar.txt'; + $connections = 1; + + $curl = new Curl(); + $curl->setHeader('X-DEBUG-TEST', 'retry'); + $curl->setCookieFile($cookie_jar); + $curl->setCookieJar($cookie_jar); + $response = $curl->fastDownload($url, $downloaded_local_file_path, $connections); + + $this->assertFalse($response); + $this->assertTrue(unlink($cookie_jar)); + } + + public function testFastDownloadSuccessSuccessFail() + { + $url = Test::TEST_URL . '?failures=0,0,1'; + $downloaded_local_file_path = \Helper\get_tmp_file_path(); + $cookie_jar = __DIR__ . '/cookiejar.txt'; + $connections = 2; + + $curl = new Curl(); + $curl->setHeader('X-DEBUG-TEST', 'retry'); + $curl->setCookieFile($cookie_jar); + $curl->setCookieJar($cookie_jar); + $response = $curl->fastDownload($url, $downloaded_local_file_path, $connections); + + $this->assertFalse($response); + $this->assertTrue(unlink($cookie_jar)); + } + public function testMaxFilesize() { $tests = [ @@ -990,7 +1115,8 @@ public function testReferrer() public function testResponseBody() { - foreach ([ + foreach ( + [ 'GET' => 'OK', 'POST' => 'OK', 'PUT' => 'OK', @@ -999,7 +1125,8 @@ public function testResponseBody() 'DELETE' => 'OK', 'HEAD' => '', 'OPTIONS' => 'OK', - ] as $request_method => $expected_response) { + ] as $request_method => $expected_response + ) { $curl = new Curl(); $curl->setHeader('X-DEBUG-TEST', 'response_body'); $this->assertEquals($expected_response, $curl->$request_method(Test::TEST_URL)); @@ -1368,7 +1495,8 @@ public function testPostCurlFileFormDataContentType() public function testJsonRequest() { - foreach ([ + foreach ( + [ [ [ 'key' => 'value', @@ -1386,24 +1514,29 @@ public function testJsonRequest() ], '{"key":"value","strings":["a","b","c"]}', ], - ] as $test) { + ] as $test + ) { list($data, $expected_response) = $test; $test = new Test(); $this->assertEquals($expected_response, $test->server('post_json', 'POST', json_encode($data))); - foreach ([ + foreach ( + [ 'Content-Type', 'content-type', - 'CONTENT-TYPE'] as $key) { - foreach ([ + 'CONTENT-TYPE'] as $key + ) { + foreach ( + [ 'APPLICATION/JSON', 'APPLICATION/JSON; CHARSET=UTF-8', 'APPLICATION/JSON;CHARSET=UTF-8', 'application/json', 'application/json; charset=utf-8', 'application/json;charset=UTF-8', - ] as $value) { + ] as $value + ) { $test = new Test(); $test->curl->setHeader($key, $value); $this->assertEquals($expected_response, $test->server('post_json', 'POST', json_encode($data))); @@ -1418,18 +1551,22 @@ public function testJsonRequest() public function testJsonResponse() { - foreach ([ + foreach ( + [ 'Content-Type', 'content-type', - 'CONTENT-TYPE'] as $key) { - foreach ([ + 'CONTENT-TYPE'] as $key + ) { + foreach ( + [ 'APPLICATION/JSON', 'APPLICATION/JSON; CHARSET=UTF-8', 'APPLICATION/JSON;CHARSET=UTF-8', 'application/json', 'application/json; charset=utf-8', 'application/json;charset=UTF-8', - ] as $value) { + ] as $value + ) { $test = new Test(); $test->server('json_response', 'POST', [ 'key' => $key, @@ -2729,11 +2866,14 @@ public function testXmlContentTypeDetection() public function testXmlResponse() { - foreach ([ + foreach ( + [ 'Content-Type', 'content-type', - 'CONTENT-TYPE'] as $key) { - foreach ([ + 'CONTENT-TYPE'] as $key + ) { + foreach ( + [ 'application/atom+xml; charset=UTF-8', 'application/atom+xml;charset=UTF-8', 'application/rss+xml', @@ -2748,7 +2888,8 @@ public function testXmlResponse() 'text/xml', 'text/xml; charset=utf-8', 'text/xml;charset=utf-8', - ] as $value) { + ] as $value + ) { $test = new Test(); $test->server('xml_response', 'POST', [ 'key' => $key, @@ -3046,11 +3187,17 @@ public function testCookieJarAfterClose() } /** - * @expectedException \PHPUnit\Framework\Error\Warning + * @requires PHPUnit >= 10 */ - public function testRequiredOptionCurlOptReturnTransferEmitsWarning() + #[RequiresPhpunit('>= 10')] + public function testRequiredOptionCurlOptReturnTransferEmitsWarningPHPUnit10Plus() { - $this->expectWarning(\PHPUnit\Framework\Error\Warning::class); + set_error_handler(static function (int $errno, string $errstr): never { + restore_error_handler(); + throw new \Exception($errstr, $errno); + }, E_USER_WARNING); + + $this->expectExceptionMessage('CURLOPT_RETURNTRANSFER is a required option'); $curl = new Curl(); $curl->setOpt(CURLOPT_RETURNTRANSFER, false); @@ -3242,12 +3389,6 @@ public function testRequestMethodSuccessiveSearchRequests() public function testMemoryLeak() { - // Skip memory leak test failing for PHP 7. - // "Failed asserting that 8192 is less than 1000." - if (getenv('CI_PHP_VERSION') === '7.0') { - $this->markTestSkipped(); - } - ob_start(); echo '['; for ($i = 0; $i < 10; $i++) { @@ -3256,7 +3397,12 @@ public function testMemoryLeak() } echo '{"before":' . memory_get_usage() . ','; $curl = new Curl(); + + // Unset the $curl object instead of calling $curl->close(). Calling + // unset($curl) should trigger the clean up: __destruct() which + // calls $curl->close(). unset($curl); + echo '"after":' . memory_get_usage() . '}'; sleep(1); } @@ -3402,7 +3548,7 @@ public function testBuildUrlArgs() [ 'args' => [ 'url' => 'https://www.example.com/?a=base', - 'mixed_data' => 'b=2&c=3' + 'mixed_data' => 'b=2&c=3', ], 'expected' => 'https://www.example.com/?a=base&b=2&c=3', ], @@ -3808,7 +3954,7 @@ public function testMock() $curl->expects($this->once()) ->method('getRawResponse') - ->will($this->returnValue('[]')); + ->willReturn('[]'); $this->assertEquals('[]', $curl->getRawResponse()); } @@ -3864,7 +4010,7 @@ public function testJsonSerializable() $expected_response = '{"name":"Alice","email":"alice@example.com"}'; - $user = new \Helper\User('Alice', 'alice@example.com'); + $user = new User('Alice', 'alice@example.com'); $this->assertEquals($expected_response, json_encode($user)); $test = new Test(); @@ -4058,7 +4204,7 @@ public function testSetMaximumRedirects() $this->assertEquals(3, $curl->getOpt(CURLOPT_MAXREDIRS)); } - public function testDiagnose() + public function testDiagnoseOutputGet() { // Test diagnose() with default parameters. $test_1 = new Test(); @@ -4081,24 +4227,424 @@ public function testDiagnose() $test_3_output = ob_get_contents(); ob_end_clean(); - foreach ([ + foreach ( + [ '--- Begin PHP Curl Class diagnostic output ---', 'PHP Curl Class version: ' . Curl::VERSION, 'PHP version: ' . PHP_VERSION, + 'CURLOPT_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS)', + 'CURLOPT_REDIR_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS)', + 'CURLOPT_HTTPGET: true', 'Sent an HTTP GET request ', 'Request contained no body.', 'Received an HTTP status code of 401.', 'Received an HTTP 401 error response with message "HTTP/1.1 401 Unauthorized".', 'Received an empty response body (response="").', '--- End PHP Curl Class diagnostic output ---', - ] as $expected_string) { + ] as $expected_string + ) { $this->assertStringContainsString($expected_string, $test_1_output); $this->assertStringContainsString($expected_string, $test_2_output); $this->assertStringContainsString($expected_string, $test_3_output); } } - public function testStopRequest() { + public function testDiagnoseOutputPost() + { + $test = new Test(); + $test->server('error_message', 'POST'); + $test_output = $test->curl->diagnose(true); + + foreach ( + [ + '--- Begin PHP Curl Class diagnostic output ---', + 'PHP Curl Class version: ' . Curl::VERSION, + 'PHP version: ' . PHP_VERSION, + 'CURLOPT_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS)', + 'CURLOPT_REDIR_PROTOCOLS: 3 (CURLPROTO_HTTP | CURLPROTO_HTTPS)', + 'CURLOPT_POST: true', + 'Sent an HTTP POST request ', + 'Request contained no body.', + 'Received an HTTP status code of 401.', + 'Received an HTTP 401 error response with message "HTTP/1.1 401 Unauthorized".', + 'Received an empty response body (response="").', + '--- End PHP Curl Class diagnostic output ---', + ] as $expected_string + ) { + $this->assertStringContainsString($expected_string, $test_output); + } + } + + public function testDiagnoseAllowHeader() + { + $tests = [ + [ + 'http_method' => 'GET', + 'allow_header_name' => 'Allow', + 'allow_header_value' => 'POST, OPTIONS', + 'expected' => + 'Warning: An HTTP GET request was made, but only the following request types are allowed: ' . + 'POST, OPTIONS', + ], + [ + 'http_method' => 'GET', + 'allow_header_name' => 'allow', + 'allow_header_value' => 'OPTIONS, POST', + 'expected' => + 'Warning: An HTTP GET request was made, but only the following request types are allowed: ' . + 'OPTIONS, POST', + ], + [ + 'http_method' => 'POST', + 'allow_header_name' => 'allow', + 'allow_header_value' => 'GET, OPTIONS', + 'expected' => + 'Warning: An HTTP POST request was made, but only the following request types are allowed: ' . + 'GET, OPTIONS', + ], + [ + 'http_method' => 'POST', + 'allow_header_name' => 'allow', + 'allow_header_value' => 'GET,OPTIONS', + 'expected' => + 'Warning: An HTTP POST request was made, but only the following request types are allowed: ' . + 'GET, OPTIONS', + ], + [ + 'http_method' => 'POST', + 'allow_header_name' => 'ALLOW', + 'allow_header_value' => 'get,options', + 'expected' => + 'Warning: An HTTP POST request was made, but only the following request types are allowed: ' . + 'GET, OPTIONS', + ], + ]; + + foreach ($tests as $test_case) { + $test = new Test(); + $test->server('json_response', $test_case['http_method'], [ + 'key' => $test_case['allow_header_name'], + 'value' => $test_case['allow_header_value'], + ]); + + $test_output = $test->curl->diagnose(true); + $this->assertStringContainsString($test_case['expected'], $test_output); + } + } + + public function testDiagnoseBitmaskOptions() + { + $tests = [ + [ + 'CURLOPT_PROTOCOLS' => [ + 'option' => CURLOPT_PROTOCOLS, + 'bitmask' => CURLPROTO_HTTP | CURLPROTO_HTTPS, + 'expected' => 'CURLPROTO_HTTP | CURLPROTO_HTTPS', + ], + 'CURLOPT_REDIR_PROTOCOLS' => [ + 'option' => CURLOPT_REDIR_PROTOCOLS, + 'bitmask' => CURLPROTO_HTTPS | CURLPROTO_HTTP, + 'expected' => 'CURLPROTO_HTTP | CURLPROTO_HTTPS', + ], + ], + [ + 'CURLOPT_PROTOCOLS' => [ + 'option' => CURLOPT_PROTOCOLS, + 'bitmask' => CURLPROTO_TELNET | CURLPROTO_FTPS, + 'expected' => 'CURLPROTO_FTPS | CURLPROTO_TELNET', + ], + 'CURLOPT_REDIR_PROTOCOLS' => [ + 'option' => CURLOPT_REDIR_PROTOCOLS, + 'bitmask' => CURLPROTO_SCP | CURLPROTO_LDAP | CURLPROTO_LDAPS, + 'expected' => 'CURLPROTO_LDAP | CURLPROTO_LDAPS | CURLPROTO_SCP', + ], + ], + [ + 'CURLOPT_PROTOCOLS' => [ + 'option' => CURLOPT_PROTOCOLS, + 'bitmask' => CURLPROTO_ALL, + 'expected' => 'CURLPROTO_ALL', + ], + 'CURLOPT_REDIR_PROTOCOLS' => [ + 'option' => CURLOPT_REDIR_PROTOCOLS, + 'bitmask' => CURLPROTO_ALL, + 'expected' => 'CURLPROTO_ALL', + ], + ], + [ + 'CURLOPT_PROXYAUTH' => [ + 'option' => CURLOPT_PROXYAUTH, + 'bitmask' => CURLAUTH_BASIC, + 'expected' => 'CURLAUTH_BASIC', + ], + ], + [ + 'CURLOPT_PROXYAUTH' => [ + 'option' => CURLOPT_PROXYAUTH, + 'bitmask' => CURLAUTH_DIGEST | CURLAUTH_NTLM, + 'expected' => 'CURLAUTH_DIGEST | CURLAUTH_NTLM', + ], + ], + [ + 'CURLOPT_PROXYAUTH' => [ + 'option' => CURLOPT_PROXYAUTH, + 'bitmask' => CURLAUTH_ANY, + 'expected' => 'CURLAUTH_ANY', + ], + ], + [ + 'CURLOPT_PROXYAUTH' => [ + 'option' => CURLOPT_PROXYAUTH, + 'bitmask' => CURLAUTH_ANYSAFE, + 'expected' => 'CURLAUTH_ANYSAFE', + ], + ], + [ + 'CURLOPT_HTTPAUTH' => [ + 'option' => CURLOPT_HTTPAUTH, + 'bitmask' => CURLAUTH_BASIC, + 'expected' => 'CURLAUTH_BASIC', + ], + ], + [ + 'CURLOPT_HTTPAUTH' => [ + 'option' => CURLOPT_HTTPAUTH, + 'bitmask' => CURLAUTH_DIGEST | CURLAUTH_NTLM, + 'expected' => 'CURLAUTH_DIGEST | CURLAUTH_NTLM', + ], + ], + [ + 'CURLOPT_HTTPAUTH' => [ + 'option' => CURLOPT_HTTPAUTH, + 'bitmask' => CURLAUTH_ANY, + 'expected' => 'CURLAUTH_ANY', + ], + ], + [ + 'CURLOPT_HTTPAUTH' => [ + 'option' => CURLOPT_HTTPAUTH, + 'bitmask' => CURLAUTH_ANYSAFE, + 'expected' => 'CURLAUTH_ANYSAFE', + ], + ], + [ + 'CURLOPT_SSL_OPTIONS' => [ + 'option' => CURLOPT_SSL_OPTIONS, + 'bitmask' => CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_REVOKE, + 'expected' => 'CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_REVOKE', + ], + ], + [ + 'CURLOPT_SSH_AUTH_TYPES' => [ + 'option' => CURLOPT_SSH_AUTH_TYPES, + 'bitmask' => CURLSSH_AUTH_KEYBOARD | CURLSSH_AUTH_PASSWORD | CURLSSH_AUTH_PUBLICKEY, + 'expected' => 'CURLSSH_AUTH_KEYBOARD | CURLSSH_AUTH_PASSWORD | CURLSSH_AUTH_PUBLICKEY', + ], + ], + ]; + + if (defined('CURLOPT_PROXY_SSL_OPTIONS') && defined('CURLSSLOPT_NO_PARTIALCHAIN')) { + $tests[] = [ + 'CURLOPT_PROXY_SSL_OPTIONS' => [ + 'option' => CURLOPT_PROXY_SSL_OPTIONS, + 'bitmask' => CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_REVOKE | CURLSSLOPT_NO_PARTIALCHAIN, + 'expected' => 'CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_PARTIALCHAIN | CURLSSLOPT_NO_REVOKE', + ], + ]; + } + + foreach ($tests as $test_case) { + $test = new Test(); + foreach ($test_case as $option_name => $values) { + $test->curl->setOpt($values['option'], $values['bitmask']); + } + $test->server('json_response', 'GET'); + $test_output = $test->curl->diagnose(true); + + foreach ($test_case as $option_name => $values) { + $expected = $option_name . ': ' . $values['bitmask']; + if (isset($values['expected'])) { + $expected .= ' (' . $values['expected'] . ')'; + } + $expected .= "\n"; + $this->assertStringContainsString($expected, $test_output); + } + } + } + + public function testDiagnoseBitmaskOptionsNonPositive() + { + // CURLOPT_HTTPAUTH + // CURLAUTH_ANY: -17 + $test_1 = new Test(); + $test_1->curl->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_ANY); + $test_1->server('json_response', 'GET'); + $test_1_output = $test_1->curl->diagnose(true); + $this->assertStringContainsString('CURLOPT_HTTPAUTH: -17 (CURLAUTH_ANY)', $test_1_output); + + // CURLOPT_HTTPAUTH + // CURLAUTH_ANYSAFE: -18 + $test_2 = new Test(); + $test_2->curl->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); + $test_2->server('json_response', 'GET'); + $test_2_output = $test_2->curl->diagnose(true); + $this->assertStringContainsString('CURLOPT_HTTPAUTH: -18 (CURLAUTH_ANYSAFE)', $test_2_output); + + // CURLOPT_PROTOCOLS + // CURLPROTO_ALL: -1 + $test_3 = new Test(); + $test_3->curl->setOpt(CURLOPT_PROTOCOLS, CURLPROTO_ALL); + $test_3->server('json_response', 'GET'); + $test_3_output = $test_3->curl->diagnose(true); + $this->assertStringContainsString('CURLOPT_PROTOCOLS: -1 (CURLPROTO_ALL)', $test_3_output); + + // CURLOPT_SSH_AUTH_TYPES + // CURLSSH_AUTH_ANY: -1 + $test_4 = new Test(); + $test_4->curl->setOpt(CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_ANY); + $test_4->server('json_response', 'GET'); + $test_4_output = $test_4->curl->diagnose(true); + $this->assertStringContainsString('CURLOPT_SSH_AUTH_TYPES: -1 (CURLSSH_AUTH_ANY)', $test_4_output); + + // CURLOPT_SSH_AUTH_TYPES + // CURLSSH_AUTH_DEFAULT: -1 + $test_5 = new Test(); + $test_5->curl->setOpt(CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_DEFAULT); + $test_5->server('json_response', 'GET'); + $test_5_output = $test_5->curl->diagnose(true); + $this->assertStringContainsString('CURLOPT_SSH_AUTH_TYPES: -1 (CURLSSH_AUTH_ANY)', $test_5_output); + } + + public function testDiagnoseErrorMessage() + { + $tests = [ + [ + 'body' => + json_encode([ + 'error' => [ + 'code' => 503, + 'message' => 'The service is currently unavailable.', + 'errors' => [ + [ + 'message' => 'The service is currently unavailable.', + 'domain' => 'global', + 'reason' => 'backendError', + ], + ], + 'status' => 'UNAVAILABLE', + ], + ], JSON_PRETTY_PRINT), + 'expects' => [ + 'Found 2 messages in response:', + 'code: 503', + 'message: The service is currently unavailable.', + ], + ], + [ + 'body' => + json_encode([ + 'errors' => [ + [ + 'id' => 'invalid_token', + 'message' => 'The access token is invalid', + ], + ], + ]), + 'expects' => [ + 'Found 1 message in response:', + 'message: The access token is invalid', + ], + ], + [ + 'body' => + json_encode([ + 'apiVersion' => '2.0', + 'error' => [ + 'code' => 404, + 'message' => 'File Not Found', + 'errors' => [ + [ + 'domain' => 'Calendar', + 'reason' => 'ResourceNotFoundException', + 'message' => 'File Not Found', + ], + ], + ], + ]), + 'expects' => [ + 'Found 2 messages in response:', + 'code: 404', + 'message: File Not Found', + ], + ], + ]; + foreach ($tests as $test_case) { + $test = new Test(); + $test->server('json_response', 'POST', [ + 'body' => $test_case['body'], + ]); + + $test_output = $test->curl->diagnose(true); + + foreach ($test_case['expects'] as $expect) { + $this->assertStringContainsString($expect, $test_output); + } + } + } + + public function testDiagnoseRequestHeadersEmptyWarning() + { + $expect = 'Warning: Request headers (Curl::requestHeaders) are expected to be empty'; + + $test_1 = new Test(); + $test_1->curl->verbose(); + $test_1->server('json_response', 'GET'); + $test_1_output = $test_1->curl->diagnose(true); + $this->assertStringContainsString($expect, $test_1_output); + + $test_2 = new Test(); + $test_2->curl->setOpt(CURLOPT_VERBOSE, true); + $test_2->curl->setOpt(CURLINFO_HEADER_OUT, false); + $test_2->server('json_response', 'GET'); + $test_2_output = $test_2->curl->diagnose(true); + $this->assertStringContainsString($expect, $test_2_output); + + $test_3 = new Test(); + $test_3->curl->setOpt(CURLOPT_VERBOSE, true); + $test_3->curl->setOpt(CURLINFO_HEADER_OUT, 0); + $test_3->server('json_response', 'GET'); + $test_3_output = $test_3->curl->diagnose(true); + $this->assertStringContainsString($expect, $test_3_output); + + $test_4 = new Test(); + $test_4->curl->setOpt(CURLOPT_VERBOSE, 1); + $test_4->curl->setOpt(CURLINFO_HEADER_OUT, false); + $test_4->server('json_response', 'GET'); + $test_4_output = $test_4->curl->diagnose(true); + $this->assertStringContainsString($expect, $test_4_output); + + $test_5 = new Test(); + $test_5->curl->setOpt(CURLOPT_VERBOSE, 1); + $test_5->curl->setOpt(CURLINFO_HEADER_OUT, 0); + $test_5->server('json_response', 'GET'); + $test_5_output = $test_5->curl->diagnose(true); + $this->assertStringContainsString($expect, $test_5_output); + } + + public function testDiagnoseContentTypeMissing() + { + $test = new Test(); + $test->server('json_response', 'POST', [ + 'remove-content-type-header' => '', + ]); + $test_output = $test->curl->diagnose(true); + + $this->assertFalse(isset($test->curl->responseHeaders['content-type'])); + $this->assertStringContainsString('Response did not set a content type', $test_output); + } + + public function testStopRequest() + { $response_length_bytes = 1e6; // 1e6 = 1 megabyte $stop_request_early = function ($ch, $header) { @@ -4178,6 +4724,22 @@ public function testPostDataArray() $this->assertEquals('POST / HTTP/1.1', $curl->requestHeaders['Request-Line']); $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); + $this->assertEquals('key=value', $curl->response); + } + + public function testPostDataArrayNullValues() + { + $data = [ + 'key1' => 'value1', + 'key2' => null, + 'key3' => 'value3', + ]; + + $curl = new Curl(); + $curl->setHeader('X-DEBUG-TEST', 'post'); + $curl->post(Test::TEST_URL, $data); + + $this->assertEquals('key1=value1&key2=&key3=value3', $curl->response); } public function testPostDataString() @@ -4272,4 +4834,285 @@ public function testSearchDataString() $this->assertEquals(Test::TEST_URL, $curl->url); $this->assertEquals(Test::TEST_URL, $curl->effectiveUrl); } + + public function testBeforeSendEachRequest() + { + // Ensure Curl::beforeSend() is called before each request including retries. + + $test = new Test(); + $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $test->curl->setRetry(5); + + $before_send_call_count = 0; + $test->curl->beforeSend(function ($instance) use (&$before_send_call_count) { + $before_send_call_count += 1; + }); + + $test->server('retry', 'GET', ['failures' => 5]); + + $this->assertEquals(6, $before_send_call_count); + $this->assertEquals(6, $test->curl->attempts); + $this->assertEquals(5, $test->curl->retries); + $this->assertFalse($test->curl->error); + } + + public function testGzipDecoding() + { + $test = new Test(); + $test->server('json_response', 'POST', [ + 'key' => 'content-encoding', + 'value' => 'gzip', + 'body' => gzencode('hello'), + ]); + $this->assertEquals('hello', $test->curl->response); + } + + public function testGzipAlreadyDecodedWithHeader() + { + $test = new Test(); + + // Send header containing all supported encoding types by setting + // CURLOPT_ENCODING to an empty string. + $test->curl->setOpt(CURLOPT_ENCODING, ''); + + $test->server('json_response', 'POST', [ + 'key' => 'content-encoding', + 'value' => 'gzip', + 'body' => gzencode('hello'), + ]); + $this->assertEquals('hello', $test->curl->response); + } + + public function testGzipAlreadyDecodedWithoutHeader() + { + $test = new Test(); + + // Send header containing all supported encoding types by setting + // CURLOPT_ENCODING to an empty string. + $test->curl->setOpt(CURLOPT_ENCODING, ''); + + $test->server('json_response', 'POST', [ + 'body' => gzencode('hello'), + ]); + $this->assertEquals('hello', $test->curl->response); + } + + public function testGzipDecodingFailureWithoutWarning() + { + $test = new Test(); + $test->server('json_response', 'POST', [ + 'headers' => [ + 'content-type: text/html; charset=utf-8', + 'content-encoding: gzip', + ], + 'body' => 'not gzip-encoded', + ]); + $this->assertEquals('text/html; charset=utf-8', $test->curl->responseHeaders['content-type']); + $this->assertEquals('gzip', $test->curl->responseHeaders['content-encoding']); + $this->assertEquals('not gzip-encoded', $test->curl->response); + } + + public function testGzipDecodingNonStringResponseWithoutError() + { + $test = new Test(); + $test->curl->setDefaultDecoder(function () { + $response = new \stdClass(); + $response->{'abc'} = 'foo'; + $response->{'123'} = 'bar'; + return $response; + }); + $test->server('json_response', 'POST', [ + 'headers' => [ + 'content-type: text/html; charset=utf-8', + 'content-encoding: gzip', + ], + ]); + $this->assertEquals('text/html; charset=utf-8', $test->curl->responseHeaders['content-type']); + $this->assertEquals('gzip', $test->curl->responseHeaders['content-encoding']); + $this->assertEquals('foo', $test->curl->response->{'abc'}); + $this->assertEquals('bar', $test->curl->response->{'123'}); + } + + public function testGzipContentEncoding() + { + // [ + // [ + // 'Response header content-encoding: "gzip"', + // 'Response header content-encoding: "notgzip"', + // 'Response header content-encoding: "" (empty)', + // 'Response header without content-encoding', + // ], + // [ + // 'Content is valid gzip-encoded', + // 'Content is not valid gzip-encoded', + // 'Content is not gzip-encoded', + // ], + // ] + + $tests = [ + // Response header content-encoding: "gzip" + // Content is valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => 'gzip', + 'body' => gzencode('hello'), + ], + 'expect_response' => 'hello', + ], + + // Response header content-encoding: "gzip" + // Content is not valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => 'gzip', + 'body' => 'not-gzip-encoded', + ], + 'expect_response' => 'not-gzip-encoded', + ], + + // Response header content-encoding: "gzip" + // Content is not gzip-encoded + [ + 'data' => [ + 'headers' => [ + 'content-type: text/html; charset=utf-8', + 'content-encoding: gzip', + ], + 'body' => 'not gzip-encoded', + ], + 'expect_response' => 'not gzip-encoded', + ], + + // Response header content-encoding: "notgzip" + // Content is valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => 'notgzip', + 'body' => gzencode('hello'), + ], + 'expect_response' => 'hello', + ], + + // Response header content-encoding: "notgzip" + // Content is not valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => 'notgzip', + 'body' => base64_encode('not-valid-gzip-encoded'), + ], + 'expect_response' => base64_encode('not-valid-gzip-encoded'), + ], + + // Response header content-encoding: "notgzip" + // Content is not gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => 'notgzip', + 'body' => 'not-gzip-encoded', + ], + 'expect_response' => 'not-gzip-encoded', + ], + + // Response header content-encoding: "" (empty) + // Content is valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => '', + 'body' => gzencode('hello'), + ], + 'expect_response' => 'hello', + ], + + // Response header content-encoding: "" (empty) + // Content is not valid gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => '', + 'body' => base64_encode('not-valid-gzip-encoded'), + ], + 'expect_response' => base64_encode('not-valid-gzip-encoded'), + ], + + // Response header content-encoding: "" (empty) + // Content is not gzip-encoded + [ + 'data' => [ + 'key' => 'content-encoding', + 'value' => '', + 'body' => 'not-gzip-encoded', + ], + 'expect_response' => 'not-gzip-encoded', + ], + + // Response header without content-encoding + // Content is valid gzip-encoded + [ + 'data' => [ + 'body' => gzencode('hello'), + ], + 'expect_response' => 'hello', + ], + + // Response header without content-encoding + // Content is not valid gzip-encoded + [ + 'data' => [ + 'body' => base64_encode('not-valid-gzip-encoded'), + ], + 'expect_response' => base64_encode('not-valid-gzip-encoded'), + ], + + // Response header without content-encoding + // Content is not gzip-encoded + [ + 'data' => [ + 'body' => 'not-gzip-encoded', + ], + 'expect_response' => 'not-gzip-encoded', + ], + ]; + foreach ($tests as $test_data) { + $test = new Test(); + $test->server('json_response', 'POST', $test_data['data']); + $this->assertEquals($test_data['expect_response'], $test->curl->response); + } + } + + public function testAfterSendAttemptCount() + { + $test = new Test(); + $test->curl->setRetry(10); + $test->curl->afterSend(function ($instance) { + if ($instance->attempts < 5) { + $instance->error = true; + } else { + $instance->error = false; + } + }); + $test->server('json_response', 'GET'); + $this->assertEquals(5, $test->curl->attempts); + $this->assertEquals(4, $test->curl->retries); + $this->assertFalse($test->curl->error); + } + + public function testAfterSendResponseMessage() + { + $test = new Test(); + $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $test->curl->setRetry(5); + $test->curl->afterSend(function ($instance) { + $instance->error = $instance->response->message !== '202 Accepted'; + }); + $test->server('retry', 'GET', ['failures' => 3]); + $this->assertEquals(4, $test->curl->attempts); + $this->assertEquals(3, $test->curl->retries); + $this->assertFalse($test->curl->error); + } } diff --git a/tests/PHPCurlClass/PHPMultiCurlClassTest.php b/tests/PHPCurlClass/PHPMultiCurlClassTest.php index 28cac1f0cc..088e283eb2 100644 --- a/tests/PHPCurlClass/PHPMultiCurlClassTest.php +++ b/tests/PHPCurlClass/PHPMultiCurlClassTest.php @@ -1,4 +1,6 @@ -skip_slow_tests = in_array(getenv('PHP_CURL_CLASS_SKIP_SLOW_TESTS'), ['1', 'y', 'Y']); + $this->skip_slow_tests = in_array(getenv('PHP_CURL_CLASS_SKIP_SLOW_TESTS'), ['1', 'y', 'Y'], true); } public function testMultiCurlCallback() { $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; $multi_curl = new MultiCurl(); $multi_curl->beforeSend(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertFalse($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_before_send_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_before_send_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertFalse($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -99,6 +148,7 @@ public function testMultiCurlCallback() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertFalse($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -106,6 +156,7 @@ public function testMultiCurlCallback() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertFalse($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -113,6 +164,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertFalse($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -120,6 +172,7 @@ public function testMultiCurlCallback() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertFalse($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -127,6 +180,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertFalse($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -134,41 +188,201 @@ public function testMultiCurlCallback() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertFalse($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); $search_before_send_called = true; } }); + $multi_curl->afterSend(function ($instance) use ( + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, + &$download_complete_called, + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + $request_method = \Helper\get_request_method($instance); + if ($request_method === 'DELETE') { + \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_success_called); + \PHPUnit\Framework\Assert::assertFalse($delete_error_called); + \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); + $delete_after_send_called = true; + } + if ($instance->downloadFileName !== null) { + \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_success_called); + \PHPUnit\Framework\Assert::assertFalse($download_error_called); + \PHPUnit\Framework\Assert::assertFalse($download_complete_called); + $download_after_send_called = true; + } elseif ($request_method === 'GET') { + \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_success_called); + \PHPUnit\Framework\Assert::assertFalse($get_error_called); + \PHPUnit\Framework\Assert::assertFalse($get_complete_called); + $get_after_send_called = true; + } + if ($request_method === 'HEAD') { + \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_success_called); + \PHPUnit\Framework\Assert::assertFalse($head_error_called); + \PHPUnit\Framework\Assert::assertFalse($head_complete_called); + $head_after_send_called = true; + } + if ($request_method === 'OPTIONS') { + \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_success_called); + \PHPUnit\Framework\Assert::assertFalse($options_error_called); + \PHPUnit\Framework\Assert::assertFalse($options_complete_called); + $options_after_send_called = true; + } + if ($request_method === 'PATCH') { + \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_success_called); + \PHPUnit\Framework\Assert::assertFalse($patch_error_called); + \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); + $patch_after_send_called = true; + } + if ($request_method === 'POST') { + \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_success_called); + \PHPUnit\Framework\Assert::assertFalse($post_error_called); + \PHPUnit\Framework\Assert::assertFalse($post_complete_called); + $post_after_send_called = true; + } + if ($request_method === 'PUT') { + \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_success_called); + \PHPUnit\Framework\Assert::assertFalse($put_error_called); + \PHPUnit\Framework\Assert::assertFalse($put_complete_called); + $put_after_send_called = true; + } + if ($request_method === 'SEARCH') { + \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_success_called); + \PHPUnit\Framework\Assert::assertFalse($search_error_called); + \PHPUnit\Framework\Assert::assertFalse($search_complete_called); + $search_after_send_called = true; + } + }); $multi_curl->success(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_success_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_success_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -176,6 +390,7 @@ public function testMultiCurlCallback() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -183,6 +398,7 @@ public function testMultiCurlCallback() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -190,6 +406,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -197,6 +414,7 @@ public function testMultiCurlCallback() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -204,6 +422,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -211,6 +430,7 @@ public function testMultiCurlCallback() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -239,34 +459,72 @@ public function testMultiCurlCallback() $search_error_called = true; }); $multi_curl->complete(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertTrue($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_complete_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertTrue($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_complete_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertTrue($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -274,6 +532,7 @@ public function testMultiCurlCallback() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertTrue($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -281,6 +540,7 @@ public function testMultiCurlCallback() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertTrue($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -288,6 +548,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertTrue($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -295,6 +556,7 @@ public function testMultiCurlCallback() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertTrue($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -302,6 +564,7 @@ public function testMultiCurlCallback() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertTrue($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -309,6 +572,7 @@ public function testMultiCurlCallback() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertTrue($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -318,7 +582,7 @@ public function testMultiCurlCallback() $multi_curl->addDelete(Test::TEST_URL); $download_file_path = tempnam('/tmp', 'php-curl-class.'); - $multi_curl->addDownload(Test::TEST_URL, $download_file_path)->download = true; + $multi_curl->addDownload(Test::TEST_URL, $download_file_path); $multi_curl->addGet(Test::TEST_URL); $multi_curl->addHead(Test::TEST_URL); $multi_curl->addOptions(Test::TEST_URL); @@ -329,47 +593,56 @@ public function testMultiCurlCallback() $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertTrue($delete_success_called); $this->assertFalse($delete_error_called); $this->assertTrue($delete_complete_called); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertTrue($download_success_called); $this->assertFalse($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertTrue($get_success_called); $this->assertFalse($get_error_called); $this->assertTrue($get_complete_called); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertTrue($head_success_called); $this->assertFalse($head_error_called); $this->assertTrue($head_complete_called); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertTrue($options_success_called); $this->assertFalse($options_error_called); $this->assertTrue($options_complete_called); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertTrue($patch_success_called); $this->assertFalse($patch_error_called); $this->assertTrue($patch_complete_called); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertTrue($post_success_called); $this->assertFalse($post_error_called); $this->assertTrue($post_complete_called); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertTrue($put_success_called); $this->assertFalse($put_error_called); $this->assertTrue($put_complete_called); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertTrue($search_success_called); $this->assertFalse($search_error_called); $this->assertTrue($search_complete_called); @@ -382,80 +655,127 @@ public function testMultiCurlCallbackError() } $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; $multi_curl = new MultiCurl(); $multi_curl->beforeSend(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertFalse($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_before_send_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_before_send_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertFalse($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -463,6 +783,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertFalse($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -470,6 +791,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertFalse($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -477,6 +799,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertFalse($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -484,6 +807,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertFalse($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -491,6 +815,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertFalse($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -498,12 +823,134 @@ public function testMultiCurlCallbackError() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertFalse($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); $search_before_send_called = true; } }); + $multi_curl->afterSend(function ($instance) use ( + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, + &$download_complete_called, + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + $request_method = \Helper\get_request_method($instance); + if ($request_method === 'DELETE') { + \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_success_called); + \PHPUnit\Framework\Assert::assertFalse($delete_error_called); + \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); + $delete_after_send_called = true; + } + if ($instance->downloadFileName !== null) { + \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_success_called); + \PHPUnit\Framework\Assert::assertFalse($download_error_called); + \PHPUnit\Framework\Assert::assertFalse($download_complete_called); + $download_after_send_called = true; + } elseif ($request_method === 'GET') { + \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_success_called); + \PHPUnit\Framework\Assert::assertFalse($get_error_called); + \PHPUnit\Framework\Assert::assertFalse($get_complete_called); + $get_after_send_called = true; + } + if ($request_method === 'HEAD') { + \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_success_called); + \PHPUnit\Framework\Assert::assertFalse($head_error_called); + \PHPUnit\Framework\Assert::assertFalse($head_complete_called); + $head_after_send_called = true; + } + if ($request_method === 'OPTIONS') { + \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_success_called); + \PHPUnit\Framework\Assert::assertFalse($options_error_called); + \PHPUnit\Framework\Assert::assertFalse($options_complete_called); + $options_after_send_called = true; + } + if ($request_method === 'PATCH') { + \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_success_called); + \PHPUnit\Framework\Assert::assertFalse($patch_error_called); + \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); + $patch_after_send_called = true; + } + if ($request_method === 'POST') { + \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_success_called); + \PHPUnit\Framework\Assert::assertFalse($post_error_called); + \PHPUnit\Framework\Assert::assertFalse($post_complete_called); + $post_after_send_called = true; + } + if ($request_method === 'PUT') { + \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_success_called); + \PHPUnit\Framework\Assert::assertFalse($put_error_called); + \PHPUnit\Framework\Assert::assertFalse($put_complete_called); + $put_after_send_called = true; + } + if ($request_method === 'SEARCH') { + \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_success_called); + \PHPUnit\Framework\Assert::assertFalse($search_error_called); + \PHPUnit\Framework\Assert::assertFalse($search_complete_called); + $search_after_send_called = true; + } + }); $multi_curl->success(function ($instance) use ( &$delete_success_called, &$download_success_called, @@ -525,34 +972,72 @@ public function testMultiCurlCallbackError() $search_success_called = true; }); $multi_curl->error(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_error_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_error_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -560,6 +1045,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -567,6 +1053,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -574,6 +1061,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -581,6 +1069,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -588,6 +1077,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -595,6 +1085,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -602,34 +1093,72 @@ public function testMultiCurlCallbackError() } }); $multi_curl->complete(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called, - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called, - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called, - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called, - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called, - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called, - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called, - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called, - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called, + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called, + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called, + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called, + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called, + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called, + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); $request_method = \Helper\get_request_method($instance); if ($request_method === 'DELETE') { \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertTrue($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_complete_called = true; } - if (isset($instance->download)) { + if ($instance->downloadFileName !== null) { \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertTrue($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_complete_called = true; } elseif ($request_method === 'GET') { \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertTrue($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -637,6 +1166,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'HEAD') { \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertTrue($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -644,6 +1174,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'OPTIONS') { \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertTrue($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -651,6 +1182,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PATCH') { \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertTrue($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -658,6 +1190,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'POST') { \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertTrue($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -665,6 +1198,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'PUT') { \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertTrue($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -672,6 +1206,7 @@ public function testMultiCurlCallbackError() } if ($request_method === 'SEARCH') { \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertTrue($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -694,7 +1229,7 @@ public function testMultiCurlCallbackError() $multi_curl->addDelete(Test::ERROR_URL); $download_file_path = tempnam('/tmp', 'php-curl-class.'); - $multi_curl->addDownload(Test::ERROR_URL, $download_file_path)->download = true; + $multi_curl->addDownload(Test::ERROR_URL, $download_file_path); $multi_curl->addGet(Test::ERROR_URL); $multi_curl->addHead(Test::ERROR_URL); $multi_curl->addOptions(Test::ERROR_URL); @@ -705,47 +1240,56 @@ public function testMultiCurlCallbackError() $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertFalse($delete_success_called); $this->assertTrue($delete_error_called); $this->assertTrue($delete_complete_called); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertFalse($download_success_called); $this->assertTrue($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertFalse($get_success_called); $this->assertTrue($get_error_called); $this->assertTrue($get_complete_called); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertFalse($head_success_called); $this->assertTrue($head_error_called); $this->assertTrue($head_complete_called); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertFalse($options_success_called); $this->assertTrue($options_error_called); $this->assertTrue($options_complete_called); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertFalse($patch_success_called); $this->assertTrue($patch_error_called); $this->assertTrue($patch_complete_called); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertFalse($post_success_called); $this->assertTrue($post_error_called); $this->assertTrue($post_complete_called); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertFalse($put_success_called); $this->assertTrue($put_error_called); $this->assertTrue($put_complete_called); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertFalse($search_success_called); $this->assertTrue($search_error_called); $this->assertTrue($search_complete_called); @@ -756,25 +1300,51 @@ public function testCurlCallback() $multi_curl = new MultiCurl(); $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; $delete = $multi_curl->addDelete(Test::TEST_URL); $delete->beforeSend(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_before_send_called = true; }); + $delete->afterSend(function ($instance) use ( + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_success_called); + \PHPUnit\Framework\Assert::assertFalse($delete_error_called); + \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); + $delete_after_send_called = true; + }); $delete->success(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); @@ -786,10 +1356,15 @@ public function testCurlCallback() $delete_error_called = true; }); $delete->complete(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertTrue($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); @@ -797,28 +1372,52 @@ public function testCurlCallback() }); $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; $download_file_path = tempnam('/tmp', 'php-curl-class.'); $download = $multi_curl->addDownload(Test::TEST_URL, $download_file_path); $download->beforeSend(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_before_send_called = true; }); + $download->afterSend(function ($instance) use ( + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, + &$download_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_success_called); + \PHPUnit\Framework\Assert::assertFalse($download_error_called); + \PHPUnit\Framework\Assert::assertFalse($download_complete_called); + $download_after_send_called = true; + }); $download->success(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); @@ -830,11 +1429,15 @@ public function testCurlCallback() $download_error_called = true; }); $download->complete(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertTrue($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); @@ -842,25 +1445,51 @@ public function testCurlCallback() }); $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; $get = $multi_curl->addGet(Test::TEST_URL); $get->beforeSend(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); $get_before_send_called = true; }); + $get->afterSend(function ($instance) use ( + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_success_called); + \PHPUnit\Framework\Assert::assertFalse($get_error_called); + \PHPUnit\Framework\Assert::assertFalse($get_complete_called); + $get_after_send_called = true; + }); $get->success(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -872,10 +1501,15 @@ public function testCurlCallback() $get_error_called = true; }); $get->complete(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertTrue($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -883,25 +1517,51 @@ public function testCurlCallback() }); $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; $head = $multi_curl->addHead(Test::TEST_URL); $head->beforeSend(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); $head_before_send_called = true; }); + $head->afterSend(function ($instance) use ( + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_success_called); + \PHPUnit\Framework\Assert::assertFalse($head_error_called); + \PHPUnit\Framework\Assert::assertFalse($head_complete_called); + $head_after_send_called = true; + }); $head->success(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -913,10 +1573,15 @@ public function testCurlCallback() $head_error_called = true; }); $head->complete(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertTrue($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -924,25 +1589,51 @@ public function testCurlCallback() }); $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; $options = $multi_curl->addOptions(Test::TEST_URL); $options->beforeSend(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); $options_before_send_called = true; }); + $options->afterSend(function ($instance) use ( + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_success_called); + \PHPUnit\Framework\Assert::assertFalse($options_error_called); + \PHPUnit\Framework\Assert::assertFalse($options_complete_called); + $options_after_send_called = true; + }); $options->success(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -954,10 +1645,15 @@ public function testCurlCallback() $options_error_called = true; }); $options->complete(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertTrue($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -965,25 +1661,51 @@ public function testCurlCallback() }); $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; $patch = $multi_curl->addPatch(Test::TEST_URL); $patch->beforeSend(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); $patch_before_send_called = true; }); + $patch->afterSend(function ($instance) use ( + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_success_called); + \PHPUnit\Framework\Assert::assertFalse($patch_error_called); + \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); + $patch_after_send_called = true; + }); $patch->success(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -995,10 +1717,15 @@ public function testCurlCallback() $patch_error_called = true; }); $patch->complete(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertTrue($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -1006,25 +1733,51 @@ public function testCurlCallback() }); $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; $post = $multi_curl->addPost(Test::TEST_URL); $post->beforeSend(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); $post_before_send_called = true; }); + $post->afterSend(function ($instance) use ( + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_success_called); + \PHPUnit\Framework\Assert::assertFalse($post_error_called); + \PHPUnit\Framework\Assert::assertFalse($post_complete_called); + $post_after_send_called = true; + }); $post->success(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -1036,10 +1789,15 @@ public function testCurlCallback() $post_error_called = true; }); $post->complete(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertTrue($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -1047,25 +1805,51 @@ public function testCurlCallback() }); $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; $put = $multi_curl->addPut(Test::TEST_URL); $put->beforeSend(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); $put_before_send_called = true; }); + $put->afterSend(function ($instance) use ( + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_success_called); + \PHPUnit\Framework\Assert::assertFalse($put_error_called); + \PHPUnit\Framework\Assert::assertFalse($put_complete_called); + $put_after_send_called = true; + }); $put->success(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -1077,10 +1861,15 @@ public function testCurlCallback() $put_error_called = true; }); $put->complete(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertTrue($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -1088,25 +1877,51 @@ public function testCurlCallback() }); $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; $search = $multi_curl->addSearch(Test::TEST_URL); $search->beforeSend(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); $search_before_send_called = true; }); + $search->afterSend(function ($instance) use ( + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_success_called); + \PHPUnit\Framework\Assert::assertFalse($search_error_called); + \PHPUnit\Framework\Assert::assertFalse($search_complete_called); + $search_after_send_called = true; + }); $search->success(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -1118,10 +1933,15 @@ public function testCurlCallback() $search_error_called = true; }); $search->complete(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertTrue($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -1131,47 +1951,56 @@ public function testCurlCallback() $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertTrue($delete_success_called); $this->assertFalse($delete_error_called); $this->assertTrue($delete_complete_called); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertTrue($download_success_called); $this->assertFalse($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertTrue($get_success_called); $this->assertFalse($get_error_called); $this->assertTrue($get_complete_called); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertTrue($head_success_called); $this->assertFalse($head_error_called); $this->assertTrue($head_complete_called); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertTrue($options_success_called); $this->assertFalse($options_error_called); $this->assertTrue($options_complete_called); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertTrue($patch_success_called); $this->assertFalse($patch_error_called); $this->assertTrue($patch_complete_called); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertTrue($post_success_called); $this->assertFalse($post_error_called); $this->assertTrue($post_complete_called); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertTrue($put_success_called); $this->assertFalse($put_error_called); $this->assertTrue($put_complete_called); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertTrue($search_success_called); $this->assertFalse($search_error_called); $this->assertTrue($search_complete_called); @@ -1186,40 +2015,71 @@ public function testCurlCallbackError() $multi_curl = new MultiCurl(); $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; $delete = $multi_curl->addDelete(Test::ERROR_URL); $delete->beforeSend(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_before_send_called = true; }); + $delete->afterSend(function ($instance) use ( + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($delete_success_called); + \PHPUnit\Framework\Assert::assertFalse($delete_error_called); + \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); + $delete_after_send_called = true; + }); $delete->success(function ($instance) use ( &$delete_success_called ) { $delete_success_called = true; }); $delete->error(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertFalse($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); $delete_error_called = true; }); $delete->complete(function ($instance) use ( - &$delete_before_send_called, &$delete_success_called, &$delete_error_called, &$delete_complete_called + &$delete_before_send_called, + &$delete_after_send_called, + &$delete_success_called, + &$delete_error_called, + &$delete_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($delete_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($delete_after_send_called); \PHPUnit\Framework\Assert::assertFalse($delete_success_called); \PHPUnit\Framework\Assert::assertTrue($delete_error_called); \PHPUnit\Framework\Assert::assertFalse($delete_complete_called); @@ -1227,21 +2087,41 @@ public function testCurlCallbackError() }); $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; $download_file_path = tempnam('/tmp', 'php-curl-class.'); $download = $multi_curl->addDownload(Test::ERROR_URL, $download_file_path); $download->beforeSend(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_success_called); + \PHPUnit\Framework\Assert::assertFalse($download_error_called); + \PHPUnit\Framework\Assert::assertFalse($download_complete_called); + $download_before_send_called = true; + }); + $download->afterSend(function ($instance) use ( + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, + &$download_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); - $download_before_send_called = true; + $download_after_send_called = true; }); $download->success(function ($instance) use ( &$download_success_called @@ -1249,22 +2129,30 @@ public function testCurlCallbackError() $download_success_called = true; }); $download->error(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertFalse($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); $download_error_called = true; }); $download->complete(function ($instance) use ( - &$download_before_send_called, &$download_success_called, &$download_error_called, + &$download_before_send_called, + &$download_after_send_called, + &$download_success_called, + &$download_error_called, &$download_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($download_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($download_after_send_called); \PHPUnit\Framework\Assert::assertFalse($download_success_called); \PHPUnit\Framework\Assert::assertTrue($download_error_called); \PHPUnit\Framework\Assert::assertFalse($download_complete_called); @@ -1272,40 +2160,71 @@ public function testCurlCallbackError() }); $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; $get = $multi_curl->addGet(Test::ERROR_URL); $get->beforeSend(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); $get_before_send_called = true; }); + $get->afterSend(function ($instance) use ( + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($get_success_called); + \PHPUnit\Framework\Assert::assertFalse($get_error_called); + \PHPUnit\Framework\Assert::assertFalse($get_complete_called); + $get_after_send_called = true; + }); $get->success(function ($instance) use ( &$get_success_called ) { $get_success_called = true; }); $get->error(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertFalse($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); $get_error_called = true; }); $get->complete(function ($instance) use ( - &$get_before_send_called, &$get_success_called, &$get_error_called, &$get_complete_called + &$get_before_send_called, + &$get_after_send_called, + &$get_success_called, + &$get_error_called, + &$get_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($get_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($get_after_send_called); \PHPUnit\Framework\Assert::assertFalse($get_success_called); \PHPUnit\Framework\Assert::assertTrue($get_error_called); \PHPUnit\Framework\Assert::assertFalse($get_complete_called); @@ -1313,40 +2232,71 @@ public function testCurlCallbackError() }); $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; $head = $multi_curl->addHead(Test::ERROR_URL); $head->beforeSend(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); $head_before_send_called = true; }); + $head->afterSend(function ($instance) use ( + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($head_success_called); + \PHPUnit\Framework\Assert::assertFalse($head_error_called); + \PHPUnit\Framework\Assert::assertFalse($head_complete_called); + $head_after_send_called = true; + }); $head->success(function ($instance) use ( &$head_success_called ) { $head_success_called = true; }); $head->error(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertFalse($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); $head_error_called = true; }); $head->complete(function ($instance) use ( - &$head_before_send_called, &$head_success_called, &$head_error_called, &$head_complete_called + &$head_before_send_called, + &$head_after_send_called, + &$head_success_called, + &$head_error_called, + &$head_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($head_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($head_after_send_called); \PHPUnit\Framework\Assert::assertFalse($head_success_called); \PHPUnit\Framework\Assert::assertTrue($head_error_called); \PHPUnit\Framework\Assert::assertFalse($head_complete_called); @@ -1354,40 +2304,71 @@ public function testCurlCallbackError() }); $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; $options = $multi_curl->addOptions(Test::ERROR_URL); $options->beforeSend(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); $options_before_send_called = true; }); + $options->afterSend(function ($instance) use ( + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($options_success_called); + \PHPUnit\Framework\Assert::assertFalse($options_error_called); + \PHPUnit\Framework\Assert::assertFalse($options_complete_called); + $options_after_send_called = true; + }); $options->success(function ($instance) use ( &$options_success_called ) { $options_success_called = true; }); $options->error(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertFalse($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); $options_error_called = true; }); $options->complete(function ($instance) use ( - &$options_before_send_called, &$options_success_called, &$options_error_called, &$options_complete_called + &$options_before_send_called, + &$options_after_send_called, + &$options_success_called, + &$options_error_called, + &$options_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($options_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($options_after_send_called); \PHPUnit\Framework\Assert::assertFalse($options_success_called); \PHPUnit\Framework\Assert::assertTrue($options_error_called); \PHPUnit\Framework\Assert::assertFalse($options_complete_called); @@ -1395,40 +2376,71 @@ public function testCurlCallbackError() }); $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; $patch = $multi_curl->addPatch(Test::ERROR_URL); $patch->beforeSend(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); $patch_before_send_called = true; }); + $patch->afterSend(function ($instance) use ( + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($patch_success_called); + \PHPUnit\Framework\Assert::assertFalse($patch_error_called); + \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); + $patch_after_send_called = true; + }); $patch->success(function ($instance) use ( &$patch_success_called ) { $patch_success_called = true; }); $patch->error(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertFalse($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); $patch_error_called = true; }); $patch->complete(function ($instance) use ( - &$patch_before_send_called, &$patch_success_called, &$patch_error_called, &$patch_complete_called + &$patch_before_send_called, + &$patch_after_send_called, + &$patch_success_called, + &$patch_error_called, + &$patch_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($patch_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($patch_after_send_called); \PHPUnit\Framework\Assert::assertFalse($patch_success_called); \PHPUnit\Framework\Assert::assertTrue($patch_error_called); \PHPUnit\Framework\Assert::assertFalse($patch_complete_called); @@ -1436,40 +2448,71 @@ public function testCurlCallbackError() }); $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; $post = $multi_curl->addPost(Test::ERROR_URL); $post->beforeSend(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); $post_before_send_called = true; }); + $post->afterSend(function ($instance) use ( + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($post_success_called); + \PHPUnit\Framework\Assert::assertFalse($post_error_called); + \PHPUnit\Framework\Assert::assertFalse($post_complete_called); + $post_after_send_called = true; + }); $post->success(function ($instance) use ( &$post_success_called ) { $post_sucess_called = true; }); $post->error(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertFalse($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); $post_error_called = true; }); $post->complete(function ($instance) use ( - &$post_before_send_called, &$post_success_called, &$post_error_called, &$post_complete_called + &$post_before_send_called, + &$post_after_send_called, + &$post_success_called, + &$post_error_called, + &$post_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($post_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($post_after_send_called); \PHPUnit\Framework\Assert::assertFalse($post_success_called); \PHPUnit\Framework\Assert::assertTrue($post_error_called); \PHPUnit\Framework\Assert::assertFalse($post_complete_called); @@ -1477,40 +2520,71 @@ public function testCurlCallbackError() }); $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; $put = $multi_curl->addPut(Test::ERROR_URL); $put->beforeSend(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); $put_before_send_called = true; }); + $put->afterSend(function ($instance) use ( + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($put_success_called); + \PHPUnit\Framework\Assert::assertFalse($put_error_called); + \PHPUnit\Framework\Assert::assertFalse($put_complete_called); + $put_after_send_called = true; + }); $put->success(function ($instance) use ( &$put_success_called ) { $put_success_called = true; }); $put->error(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertFalse($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); $put_error_called = true; }); $put->complete(function ($instance) use ( - &$put_before_send_called, &$put_success_called, &$put_error_called, &$put_complete_called + &$put_before_send_called, + &$put_after_send_called, + &$put_success_called, + &$put_error_called, + &$put_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($put_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($put_after_send_called); \PHPUnit\Framework\Assert::assertFalse($put_success_called); \PHPUnit\Framework\Assert::assertTrue($put_error_called); \PHPUnit\Framework\Assert::assertFalse($put_complete_called); @@ -1518,40 +2592,71 @@ public function testCurlCallbackError() }); $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; $search = $multi_curl->addSearch(Test::ERROR_URL); $search->beforeSend(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertFalse($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); $search_before_send_called = true; }); + $search->afterSend(function ($instance) use ( + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called + ) { + \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); + \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_after_send_called); + \PHPUnit\Framework\Assert::assertFalse($search_success_called); + \PHPUnit\Framework\Assert::assertFalse($search_error_called); + \PHPUnit\Framework\Assert::assertFalse($search_complete_called); + $search_after_send_called = true; + }); $search->success(function ($instance) use ( &$search_success_called ) { $search_success_called = true; }); $search->error(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertFalse($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); $search_error_called = true; }); $search->complete(function ($instance) use ( - &$search_before_send_called, &$search_success_called, &$search_error_called, &$search_complete_called + &$search_before_send_called, + &$search_after_send_called, + &$search_success_called, + &$search_error_called, + &$search_complete_called ) { \PHPUnit\Framework\Assert::assertInstanceOf('Curl\Curl', $instance); \PHPUnit\Framework\Assert::assertTrue($search_before_send_called); + \PHPUnit\Framework\Assert::assertTrue($search_after_send_called); \PHPUnit\Framework\Assert::assertFalse($search_success_called); \PHPUnit\Framework\Assert::assertTrue($search_error_called); \PHPUnit\Framework\Assert::assertFalse($search_complete_called); @@ -1561,47 +2666,56 @@ public function testCurlCallbackError() $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertFalse($delete_success_called); $this->assertTrue($delete_error_called); $this->assertTrue($delete_complete_called); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertFalse($download_success_called); $this->assertTrue($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertFalse($get_success_called); $this->assertTrue($get_error_called); $this->assertTrue($get_complete_called); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertFalse($head_success_called); $this->assertTrue($head_error_called); $this->assertTrue($head_complete_called); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertFalse($options_success_called); $this->assertTrue($options_error_called); $this->assertTrue($options_complete_called); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertFalse($patch_success_called); $this->assertTrue($patch_error_called); $this->assertTrue($patch_complete_called); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertFalse($post_success_called); $this->assertTrue($post_error_called); $this->assertTrue($post_complete_called); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertFalse($put_success_called); $this->assertTrue($put_error_called); $this->assertTrue($put_complete_called); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertFalse($search_success_called); $this->assertTrue($search_error_called); $this->assertTrue($search_complete_called); @@ -1624,6 +2738,7 @@ public function testCurlCallbackOverride() }); $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; @@ -1631,6 +2746,9 @@ public function testCurlCallbackOverride() $delete->beforeSend(function ($instance) use (&$delete_before_send_called) { $delete_before_send_called = true; }); + $delete->afterSend(function ($instance) use (&$delete_after_send_called) { + $delete_after_send_called = true; + }); $delete->success(function ($instance) use (&$delete_success_called) { $delete_success_called = true; }); @@ -1642,6 +2760,7 @@ public function testCurlCallbackOverride() }); $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; @@ -1650,6 +2769,9 @@ public function testCurlCallbackOverride() $download->beforeSend(function ($instance) use (&$download_before_send_called) { $download_before_send_called = true; }); + $download->afterSend(function ($instance) use (&$download_after_send_called) { + $download_after_send_called = true; + }); $download->success(function ($instance) use (&$download_success_called) { $download_success_called = true; }); @@ -1661,6 +2783,7 @@ public function testCurlCallbackOverride() }); $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; @@ -1668,6 +2791,9 @@ public function testCurlCallbackOverride() $get->beforeSend(function ($instance) use (&$get_before_send_called) { $get_before_send_called = true; }); + $get->afterSend(function ($instance) use (&$get_after_send_called) { + $get_after_send_called = true; + }); $get->success(function ($instance) use (&$get_success_called) { $get_success_called = true; }); @@ -1679,6 +2805,7 @@ public function testCurlCallbackOverride() }); $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; @@ -1686,6 +2813,9 @@ public function testCurlCallbackOverride() $head->beforeSend(function ($instance) use (&$head_before_send_called) { $head_before_send_called = true; }); + $head->afterSend(function ($instance) use (&$head_after_send_called) { + $head_after_send_called = true; + }); $head->success(function ($instance) use (&$head_success_called) { $head_success_called = true; }); @@ -1697,6 +2827,7 @@ public function testCurlCallbackOverride() }); $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; @@ -1704,6 +2835,9 @@ public function testCurlCallbackOverride() $options->beforeSend(function ($instance) use (&$options_before_send_called) { $options_before_send_called = true; }); + $options->afterSend(function ($instance) use (&$options_after_send_called) { + $options_after_send_called = true; + }); $options->success(function ($instance) use (&$options_success_called) { $options_success_called = true; }); @@ -1715,6 +2849,7 @@ public function testCurlCallbackOverride() }); $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; @@ -1722,6 +2857,9 @@ public function testCurlCallbackOverride() $patch->beforeSend(function ($instance) use (&$patch_before_send_called) { $patch_before_send_called = true; }); + $patch->afterSend(function ($instance) use (&$patch_after_send_called) { + $patch_after_send_called = true; + }); $patch->success(function ($instance) use (&$patch_success_called) { $patch_success_called = true; }); @@ -1733,6 +2871,7 @@ public function testCurlCallbackOverride() }); $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; @@ -1740,6 +2879,9 @@ public function testCurlCallbackOverride() $post->beforeSend(function ($instance) use (&$post_before_send_called) { $post_before_send_called = true; }); + $post->afterSend(function ($instance) use (&$post_after_send_called) { + $post_after_send_called = true; + }); $post->success(function ($instance) use (&$post_success_called) { $post_success_called = true; }); @@ -1751,6 +2893,7 @@ public function testCurlCallbackOverride() }); $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; @@ -1758,6 +2901,9 @@ public function testCurlCallbackOverride() $put->beforeSend(function ($instance) use (&$put_before_send_called) { $put_before_send_called = true; }); + $put->afterSend(function ($instance) use (&$put_after_send_called) { + $put_after_send_called = true; + }); $put->success(function ($instance) use (&$put_success_called) { $put_success_called = true; }); @@ -1769,6 +2915,7 @@ public function testCurlCallbackOverride() }); $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; @@ -1776,6 +2923,9 @@ public function testCurlCallbackOverride() $search->beforeSend(function ($instance) use (&$search_before_send_called) { $search_before_send_called = true; }); + $search->afterSend(function ($instance) use (&$search_after_send_called) { + $search_after_send_called = true; + }); $search->success(function ($instance) use (&$search_success_called) { $search_success_called = true; }); @@ -1789,47 +2939,56 @@ public function testCurlCallbackOverride() $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertTrue($delete_success_called); $this->assertFalse($delete_error_called); $this->assertTrue($delete_complete_called); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertTrue($download_success_called); $this->assertFalse($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertTrue($get_success_called); $this->assertFalse($get_error_called); $this->assertTrue($get_complete_called); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertTrue($head_success_called); $this->assertFalse($head_error_called); $this->assertTrue($head_complete_called); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertTrue($options_success_called); $this->assertFalse($options_error_called); $this->assertTrue($options_complete_called); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertTrue($patch_success_called); $this->assertFalse($patch_error_called); $this->assertTrue($patch_complete_called); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertTrue($post_success_called); $this->assertFalse($post_error_called); $this->assertTrue($post_complete_called); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertTrue($put_success_called); $this->assertFalse($put_error_called); $this->assertTrue($put_complete_called); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertTrue($search_success_called); $this->assertFalse($search_error_called); $this->assertTrue($search_complete_called); @@ -1838,6 +2997,7 @@ public function testCurlCallbackOverride() public function testCurlCallbackAddedAfter() { $delete_before_send_called = false; + $delete_after_send_called = false; $delete_success_called = false; $delete_error_called = false; $delete_complete_called = false; @@ -1846,6 +3006,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$delete_before_send_called) { $delete_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$delete_after_send_called) { + $delete_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$delete_success_called) { $delete_success_called = true; }); @@ -1857,11 +3020,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($delete_before_send_called); + $this->assertTrue($delete_after_send_called); $this->assertTrue($delete_success_called); $this->assertFalse($delete_error_called); $this->assertTrue($delete_complete_called); $download_before_send_called = false; + $download_after_send_called = false; $download_success_called = false; $download_error_called = false; $download_complete_called = false; @@ -1871,6 +3036,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$download_before_send_called) { $download_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$download_after_send_called) { + $download_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$download_success_called) { $download_success_called = true; }); @@ -1882,12 +3050,14 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertTrue($download_success_called); $this->assertFalse($download_error_called); $this->assertTrue($download_complete_called); $this->assertTrue(unlink($download_file_path)); $get_before_send_called = false; + $get_after_send_called = false; $get_success_called = false; $get_error_called = false; $get_complete_called = false; @@ -1896,6 +3066,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$get_before_send_called) { $get_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$get_after_send_called) { + $get_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$get_success_called) { $get_success_called = true; }); @@ -1907,11 +3080,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($get_before_send_called); + $this->assertTrue($get_after_send_called); $this->assertTrue($get_success_called); $this->assertFalse($get_error_called); $this->assertTrue($get_complete_called); $head_before_send_called = false; + $head_after_send_called = false; $head_success_called = false; $head_error_called = false; $head_complete_called = false; @@ -1920,6 +3095,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$head_before_send_called) { $head_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$head_after_send_called) { + $head_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$head_success_called) { $head_success_called = true; }); @@ -1931,11 +3109,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($head_before_send_called); + $this->assertTrue($head_after_send_called); $this->assertTrue($head_success_called); $this->assertFalse($head_error_called); $this->assertTrue($head_complete_called); $options_before_send_called = false; + $options_after_send_called = false; $options_success_called = false; $options_error_called = false; $options_complete_called = false; @@ -1944,6 +3124,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$options_before_send_called) { $options_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$options_after_send_called) { + $options_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$options_success_called) { $options_success_called = true; }); @@ -1955,11 +3138,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($options_before_send_called); + $this->assertTrue($options_after_send_called); $this->assertTrue($options_success_called); $this->assertFalse($options_error_called); $this->assertTrue($options_complete_called); $patch_before_send_called = false; + $patch_after_send_called = false; $patch_success_called = false; $patch_error_called = false; $patch_complete_called = false; @@ -1968,6 +3153,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$patch_before_send_called) { $patch_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$patch_after_send_called) { + $patch_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$patch_success_called) { $patch_success_called = true; }); @@ -1979,11 +3167,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($patch_before_send_called); + $this->assertTrue($patch_after_send_called); $this->assertTrue($patch_success_called); $this->assertFalse($patch_error_called); $this->assertTrue($patch_complete_called); $post_before_send_called = false; + $post_after_send_called = false; $post_success_called = false; $post_error_called = false; $post_complete_called = false; @@ -1992,6 +3182,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$post_before_send_called) { $post_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$post_after_send_called) { + $post_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$post_success_called) { $post_success_called = true; }); @@ -2003,11 +3196,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($post_before_send_called); + $this->assertTrue($post_after_send_called); $this->assertTrue($post_success_called); $this->assertFalse($post_error_called); $this->assertTrue($post_complete_called); $put_before_send_called = false; + $put_after_send_called = false; $put_success_called = false; $put_error_called = false; $put_complete_called = false; @@ -2016,6 +3211,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$put_before_send_called) { $put_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$put_after_send_called) { + $put_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$put_success_called) { $put_success_called = true; }); @@ -2027,11 +3225,13 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($put_before_send_called); + $this->assertTrue($put_after_send_called); $this->assertTrue($put_success_called); $this->assertFalse($put_error_called); $this->assertTrue($put_complete_called); $search_before_send_called = false; + $search_after_send_called = false; $search_success_called = false; $search_error_called = false; $search_complete_called = false; @@ -2040,6 +3240,9 @@ public function testCurlCallbackAddedAfter() $multi_curl->beforeSend(function ($instance) use (&$search_before_send_called) { $search_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$search_after_send_called) { + $search_after_send_called = true; + }); $multi_curl->success(function ($instance) use (&$search_success_called) { $search_success_called = true; }); @@ -2051,6 +3254,7 @@ public function testCurlCallbackAddedAfter() }); $multi_curl->start(); $this->assertTrue($search_before_send_called); + $this->assertTrue($search_after_send_called); $this->assertTrue($search_success_called); $this->assertFalse($search_error_called); $this->assertTrue($search_complete_called); @@ -2059,31 +3263,35 @@ public function testCurlCallbackAddedAfter() public function testSetOptAndSetOptOverride() { $multi_curl_user_agent = 'multi curl user agent'; - $curl_user_agent = 'curl user agent'; + $curl_user_agent_2 = 'curl user agent 2'; + $curl_user_agent_3 = 'curl user agent 3'; $data = ['key' => 'HTTP_USER_AGENT']; $multi_curl = new MultiCurl(); $multi_curl->setHeader('X-DEBUG-TEST', 'server'); + $multi_curl->setOpt(CURLOPT_USERAGENT, $multi_curl_user_agent); $get_1 = $multi_curl->addGet(Test::TEST_URL, $data); $get_1->complete(function ($instance) use ($multi_curl_user_agent) { \PHPUnit\Framework\Assert::assertEquals($multi_curl_user_agent, $instance->getOpt(CURLOPT_USERAGENT)); + \PHPUnit\Framework\Assert::assertEquals($multi_curl_user_agent, $instance->response); }); $get_2 = $multi_curl->addGet(Test::TEST_URL, $data); - $get_2->complete(function ($instance) use ($multi_curl_user_agent) { - \PHPUnit\Framework\Assert::assertEquals($multi_curl_user_agent, $instance->getOpt(CURLOPT_USERAGENT)); - \PHPUnit\Framework\Assert::assertEquals($multi_curl_user_agent, $instance->response); + $get_2->setOpt(CURLOPT_USERAGENT, $curl_user_agent_2); + $get_2->complete(function ($instance) use ($curl_user_agent_2) { + \PHPUnit\Framework\Assert::assertEquals($curl_user_agent_2, $instance->getOpt(CURLOPT_USERAGENT)); + \PHPUnit\Framework\Assert::assertEquals($curl_user_agent_2, $instance->response); }); $get_3 = $multi_curl->addGet(Test::TEST_URL, $data); - $get_3->beforeSend(function ($instance) use ($curl_user_agent) { - $instance->setOpt(CURLOPT_USERAGENT, $curl_user_agent); + $get_3->beforeSend(function ($instance) use ($curl_user_agent_3) { + $instance->setOpt(CURLOPT_USERAGENT, $curl_user_agent_3); }); - $get_3->complete(function ($instance) use ($curl_user_agent) { - \PHPUnit\Framework\Assert::assertEquals($curl_user_agent, $instance->getOpt(CURLOPT_USERAGENT)); - \PHPUnit\Framework\Assert::assertEquals($curl_user_agent, $instance->response); + $get_3->complete(function ($instance) use ($curl_user_agent_3) { + \PHPUnit\Framework\Assert::assertEquals($curl_user_agent_3, $instance->getOpt(CURLOPT_USERAGENT)); + \PHPUnit\Framework\Assert::assertEquals($curl_user_agent_3, $instance->response); }); $multi_curl->start(); @@ -2306,9 +3514,77 @@ public function testSetCookies() $this->assertEquals('yummy', $get_2->responseCookies['mycookie']); } + public function testSetCookieStringMultiCurl() + { + $cookie_string = 'fruit=apple; color=red'; + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'cookie'); + $multi_curl->setCookieString($cookie_string); + + $get_1 = $multi_curl->addGet(Test::TEST_URL); + $get_1->complete(function ($instance) use ($cookie_string) { + \PHPUnit\Framework\Assert::assertEquals('fruit=apple&color=red', $instance->response); + \PHPUnit\Framework\Assert::assertEquals($cookie_string, $instance->getOpt(CURLOPT_COOKIE)); + }); + + $multi_curl->start(); + } + + public function testSetCookieStringCurl() + { + $cookie_string = 'fruit=apple; color=red'; + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'cookie'); + + $get_1 = $multi_curl->addGet(Test::TEST_URL); + $get_1->setCookieString($cookie_string); + $get_1->complete(function ($instance) use ($cookie_string) { + \PHPUnit\Framework\Assert::assertEquals('fruit=apple&color=red', $instance->response); + \PHPUnit\Framework\Assert::assertEquals($cookie_string, $instance->getOpt(CURLOPT_COOKIE)); + }); + + $multi_curl->start(); + } + + public function testSetCookieString() + { + $cookie_string_1 = 'fruit=apple; color=red'; + $cookie_string_2 = 'fruit=banana; color=yellow'; + $cookie_string_3 = 'fruit=orange; color=orange'; + + $multi_curl = new MultiCurl(); + $multi_curl->setHeader('X-DEBUG-TEST', 'cookie'); + $multi_curl->setCookieString($cookie_string_1); + + $get_1 = $multi_curl->addGet(Test::TEST_URL); + $get_1->complete(function ($instance) use ($cookie_string_1) { + \PHPUnit\Framework\Assert::assertEquals('fruit=apple&color=red', $instance->response); + \PHPUnit\Framework\Assert::assertEquals($cookie_string_1, $instance->getOpt(CURLOPT_COOKIE)); + }); + + $get_2 = $multi_curl->addGet(Test::TEST_URL); + $get_2->setCookieString($cookie_string_2); + $get_2->complete(function ($instance) use ($cookie_string_2) { + \PHPUnit\Framework\Assert::assertEquals('fruit=banana&color=yellow', $instance->response); + \PHPUnit\Framework\Assert::assertEquals($cookie_string_2, $instance->getOpt(CURLOPT_COOKIE)); + }); + + $get_3 = $multi_curl->addGet(Test::TEST_URL); + $get_3->setCookieString($cookie_string_3); + $get_3->complete(function ($instance) use ($cookie_string_3) { + \PHPUnit\Framework\Assert::assertEquals('fruit=orange&color=orange', $instance->response); + \PHPUnit\Framework\Assert::assertEquals($cookie_string_3, $instance->getOpt(CURLOPT_COOKIE)); + }); + + $multi_curl->start(); + } + public function testJsonRequest() { - foreach ([ + foreach ( + [ [ [ 'key' => 'value', @@ -2326,7 +3602,8 @@ public function testJsonRequest() ], '{"key":"value","strings":["a","b","c"]}', ], - ] as $test) { + ] as $test + ) { list($data, $expected_response) = $test; $multi_curl = new MultiCurl(); @@ -2337,18 +3614,22 @@ public function testJsonRequest() $multi_curl->addPost(Test::TEST_URL, json_encode($data)); $multi_curl->start(); - foreach ([ + foreach ( + [ 'Content-Type', 'content-type', - 'CONTENT-TYPE'] as $key) { - foreach ([ + 'CONTENT-TYPE'] as $key + ) { + foreach ( + [ 'APPLICATION/JSON', 'APPLICATION/JSON; CHARSET=UTF-8', 'APPLICATION/JSON;CHARSET=UTF-8', 'application/json', 'application/json; charset=utf-8', 'application/json;charset=UTF-8', - ] as $value) { + ] as $value + ) { $multi_curl = new MultiCurl(); $multi_curl->setHeader('X-DEBUG-TEST', 'post_json'); $multi_curl->setHeader($key, $value); @@ -2651,7 +3932,8 @@ public function testDownloadRange() $filesize = filesize($filename); - foreach ([ + foreach ( + [ false, 0, 1, @@ -2682,7 +3964,8 @@ public function testDownloadRange() $filesize + 2, $filesize + 3, - ] as $length) { + ] as $length + ) { $source = Test::TEST_URL; $destination = \Helper\get_tmp_file_path(); @@ -2778,17 +4061,23 @@ public function testDownloadCallbackError() } $download_before_send_called = false; + $download_after_send_called = false; $download_callback_called = false; $multi_curl = new MultiCurl(); $multi_curl->beforeSend(function ($instance) use (&$download_before_send_called) { \PHPUnit\Framework\Assert::assertFalse($download_before_send_called); $download_before_send_called = true; }); + $multi_curl->afterSend(function ($instance) use (&$download_after_send_called) { + \PHPUnit\Framework\Assert::assertFalse($download_after_send_called); + $download_after_send_called = true; + }); $multi_curl->addDownload(Test::ERROR_URL, function ($instance, $fh) use (&$download_callback_called) { $download_callback_called = true; }); $multi_curl->start(); $this->assertTrue($download_before_send_called); + $this->assertTrue($download_after_send_called); $this->assertFalse($download_callback_called); } @@ -3089,6 +4378,25 @@ public function testAddCurl() $this->assertTrue($complete_called); } + public function testAddCurlHeaders() + { + $header_fields = [ + 'X-SOME-HEADER: some-value', + 'X-ANOTHER-HEADER: another-value', + ]; + + $curl = new Curl(); + $curl->setUrl(Test::TEST_URL); + $curl->setOpt(CURLOPT_HTTPHEADER, $header_fields); + + $this->assertEquals($header_fields, $curl->getOpt(CURLOPT_HTTPHEADER)); + + $multi_curl = new MultiCurl(); + $multi_curl->addCurl(http://github.com/$curl); + + $this->assertEquals($header_fields, $curl->getOpt(CURLOPT_HTTPHEADER)); + } + public function testSequentialId() { $multi_curl = new MultiCurl(); @@ -3471,10 +4779,10 @@ public function testSetProxiesRandomProxy() $get_2 = $multi_curl->addGet(Test::TEST_URL); $get_3 = $multi_curl->addGet(Test::TEST_URL); - // Make MultiCurl::curls accessible and MultiCurl::initHandle() + // Make MultiCurl::queuedCurls accessible and MultiCurl::initHandle() // callable. $reflector = new \ReflectionClass('\Curl\MultiCurl'); - $property = $reflector->getProperty('curls'); + $property = $reflector->getProperty('queuedCurls'); $property->setAccessible(true); $multi_curl_curls = $property->getValue($multi_curl); $multi_curl_initHandle = $reflector->getMethod('initHandle'); @@ -3511,10 +4819,10 @@ public function testSetProxiesAlreadySet() $get_2->setProxy('example.com:9999'); $get_3 = $multi_curl->addGet(Test::TEST_URL); - // Make MultiCurl::curls accessible and MultiCurl::initHandle() + // Make MultiCurl::queuedCurls accessible and MultiCurl::initHandle() // callable. $reflector = new \ReflectionClass('\Curl\MultiCurl'); - $property = $reflector->getProperty('curls'); + $property = $reflector->getProperty('queuedCurls'); $property->setAccessible(true); $multi_curl_curls = $property->getValue($multi_curl); $multi_curl_initHandle = $reflector->getMethod('initHandle'); @@ -3569,7 +4877,8 @@ public function testDisableTimeout() public function testSetRateLimitUnits() { - foreach ([ + foreach ( + [ [ 'rate_limit' => '1/s', 'expected' => [ @@ -3700,7 +5009,8 @@ public function testSetRateLimitUnits() 'interval_seconds' => '86400', ], ], - ] as $test) { + ] as $test + ) { $multi_curl = new MultiCurl(); $multi_curl->setRateLimit($test['rate_limit']); @@ -3710,7 +5020,7 @@ public function testSetRateLimitUnits() ); $this->assertEquals( $test['expected']['max_requests'], - \Helper\get_multi_curl_property_value($multi_curl, 'maxRequests') + \Helper\get_multi_curl_property_value($multi_curl, 'maxRequestsPerInterval') ); $this->assertEquals( $test['expected']['interval'], @@ -3745,6 +5055,7 @@ public function testSetRateLimitPerSecond1() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -3818,6 +5129,7 @@ public function testSetRateLimitPerSecond2() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -3889,6 +5201,7 @@ public function testSetRateLimitPerSecond3() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -3960,6 +5273,7 @@ public function testSetRateLimitPerSecond4() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4032,6 +5346,7 @@ public function testSetRateLimitPerSecond5() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4104,6 +5419,7 @@ public function testSetRateLimitPerSecond6() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4176,6 +5492,7 @@ public function testSetRateLimitPerSecond7() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4256,6 +5573,7 @@ public function testSetRateLimitPerSecond8() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4336,6 +5654,7 @@ public function testSetRateLimitPerSecond9() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('2/5s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4408,6 +5727,7 @@ public function testSetRateLimitPerSecondOnePerSecond() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setRateLimit('1/1s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { $request_stats[$instance->id] = []; @@ -4446,6 +5766,7 @@ public function testSetRateLimitFivePerThirtySecond() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('5/30s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4480,6 +5801,7 @@ public function testSetRateLimitOnePerOneMinute() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('1/1m'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4514,6 +5836,7 @@ public function testSetRateLimitThreePerOneMinute() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('3/1m'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4546,6 +5869,7 @@ public function testSetRateLimitThreePerSixtyFiveSeconds() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('3/65s'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4578,6 +5902,7 @@ public function testSetRateLimitTenPerTwoMinutes() $request_stats = []; $multi_curl = new MultiCurl(); + $multi_curl->setRequestTimeAccuracy(); $multi_curl->setHeader('X-DEBUG-TEST', 'timeout'); $multi_curl->setRateLimit('10/2m'); $multi_curl->beforeSend(function ($instance) use (&$request_stats) { @@ -4649,7 +5974,7 @@ public function testSetHeadersAssociativeArray() ]); $multi_curl->addGet(Test::TEST_URL); - $curls = \Helper\get_multi_curl_property_value($multi_curl, 'curls'); + $curls = \Helper\get_multi_curl_property_value($multi_curl, 'queuedCurls'); foreach ($curls as $curl) { $this->assertEquals([ 'Key1: Value1', @@ -4713,7 +6038,7 @@ public function testSetHeadersIndexedArray() ]); $multi_curl->addGet(Test::TEST_URL); - $curls = \Helper\get_multi_curl_property_value($multi_curl, 'curls'); + $curls = \Helper\get_multi_curl_property_value($multi_curl, 'queuedCurls'); foreach ($curls as $curl) { $this->assertEquals([ 'Key1: Value1', @@ -4941,4 +6266,144 @@ public function testSearchDataString() }); $multi_curl->start(); } + + public function testCurlStopActiveConcurrencyDefault() + { + $multi_curl = new MultiCurl(); + $request_count = 0; + $multi_curl->complete(function ($instance) use (&$request_count, $multi_curl) { + $request_count += 1; + $multi_curl->stop(); + }); + + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::TEST_URL); + + $multi_curl->start(); + + $this->assertEquals(1, $request_count); + } + + public function testCurlStopActiveConcurrencyOne() + { + $multi_curl = new MultiCurl(); + $multi_curl->setConcurrency(1); + $request_count = 0; + $multi_curl->complete(function ($instance) use (&$request_count, $multi_curl) { + $request_count += 1; + $multi_curl->stop(); + }); + + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::TEST_URL); + + $multi_curl->start(); + + $this->assertEquals(1, $request_count); + } + + public function testCurlStopOnError() + { + $multi_curl = new MultiCurl(); + $multi_curl->setConcurrency(1); + $request_count = 0; + $multi_curl->complete(function ($instance) use (&$request_count) { + $request_count += 1; + }); + $multi_curl->error(function ($instance) use ($multi_curl) { + $multi_curl->stop(); + }); + + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::TEST_URL); + $multi_curl->addGet(Test::ERROR_URL); + $multi_curl->addGet(Test::TEST_URL); + + $multi_curl->start(); + + $this->assertEquals(3, $request_count); + } + + public function testBeforeSendEachRequest() + { + // Ensure MultiCurl::beforeSend() is called before each request including retries. + + $multi_curl = new MultiCurl(); + $multi_curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $multi_curl->setHeader('X-DEBUG-TEST', 'retry'); + $multi_curl->setRetry(5); + + $before_send_call_count = 0; + $multi_curl->beforeSend(function ($instance) use (&$before_send_call_count) { + $before_send_call_count += 1; + }); + + $instance = $multi_curl->addGet(Test::TEST_URL, ['failures' => 5]); + $multi_curl->start(); + + $this->assertEquals(6, $before_send_call_count); + $this->assertEquals(6, $instance->attempts); + $this->assertEquals(5, $instance->retries); + $this->assertFalse($instance->error); + } + + public function testAfterSendEachRequest() + { + // Ensure MultiCurl::afterSend() is called before each request including retries. + + $multi_curl = new MultiCurl(); + $multi_curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $multi_curl->setHeader('X-DEBUG-TEST', 'retry'); + $multi_curl->setRetry(5); + + $after_send_call_count = 0; + $multi_curl->afterSend(function ($instance) use (&$after_send_call_count) { + $after_send_call_count += 1; + }); + + $instance = $multi_curl->addGet(Test::TEST_URL, ['failures' => 5]); + $multi_curl->start(); + + $this->assertEquals(6, $after_send_call_count); + $this->assertEquals(6, $instance->attempts); + $this->assertEquals(5, $instance->retries); + $this->assertFalse($instance->error); + } + + public function testAfterSendAttemptCount() + { + $multi_curl = new MultiCurl(); + $multi_curl->setRetry(10); + $multi_curl->afterSend(function ($instance) { + if ($instance->attempts < 5) { + $instance->error = true; + } else { + $instance->error = false; + } + }); + $multi_curl->setHeader('X-DEBUG-TEST', 'json_response'); + $instance = $multi_curl->addGet(Test::TEST_URL); + $multi_curl->start(); + $this->assertEquals(5, $instance->attempts); + $this->assertEquals(4, $instance->retries); + $this->assertFalse($instance->error); + } + + public function testAfterSendResponseMessage() + { + $multi_curl = new MultiCurl(); + $multi_curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $multi_curl->setRetry(5); + $multi_curl->afterSend(function ($instance) { + $instance->error = $instance->response->message !== '202 Accepted'; + }); + $multi_curl->setHeader('X-DEBUG-TEST', 'retry'); + $instance = $multi_curl->addGet(Test::TEST_URL, ['failures' => 3]); + $multi_curl->start(); + $this->assertEquals(4, $instance->attempts); + $this->assertEquals(3, $instance->retries); + $this->assertFalse($instance->error); + } } diff --git a/tests/PHPCurlClass/UrlTest.php b/tests/PHPCurlClass/UrlTest.php index 154f0650bb..397793565e 100644 --- a/tests/PHPCurlClass/UrlTest.php +++ b/tests/PHPCurlClass/UrlTest.php @@ -1,4 +1,6 @@ -email = $email; } + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/tests/ci.sh b/tests/ci.sh deleted file mode 100755 index fd1ea3e766..0000000000 --- a/tests/ci.sh +++ /dev/null @@ -1,128 +0,0 @@ -remove_expectWarning() { - # Fix "Call to undefined method CurlTest\CurlTest::expectWarning()". - sed -i'' -e"/->expectWarning(/d" "./PHPCurlClass/PHP"* -} - -replace_assertStringContainsString() { - # -->assertStringContainsString( - # +->assertContains( - find='->assertStringContainsString(' - replace='->assertContains(' - sed -i'' -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* -} - -phpunit_v6_5_shim() { - remove_expectWarning - replace_assertStringContainsString -} - -phpunit_v7_5_shim() { - remove_expectWarning -} - -phpunit_v8_1_shim() { - remove_expectWarning -} - -php_v7_0_shim() { - # -protected function setUp(): void - # +protected function setUp() - find='protected function setUp(): void' - replace='protected function setUp()' - sed -i'' -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* -} - -set -x - -composer self-update -composer install --prefer-source --no-interaction - -# Use composer's phpunit and phpcs by adding composer bin directory to the path environment variable. -export PATH="${PWD}/vendor/bin:${PATH}" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "${SCRIPT_DIR}" - -echo "CI_PHP_VERSION: ${CI_PHP_VERSION}" -echo "CI_PHP_FUTURE_RELEASE: ${CI_PHP_FUTURE_RELEASE}" -php -r "var_dump(phpversion());" -php -r "var_dump(curl_version());" - -# Let test server know we should allow testing. -export PHP_CURL_CLASS_TEST_MODE_ENABLED="yes" - -# Start test servers. Run servers on different ports to allow simultaneous -# requests without blocking. -server_count=7 -declare -A pids -for i in $(seq 0 $(("${server_count}" - 1))); do - port=8000 - (( port += $i )) - - php -S "127.0.0.1:${port}" -t PHPCurlClass/ &> /dev/null & - pids["${i}"]="${!}" -done - -errors=() - -source "check_syntax.sh" - -# Determine which phpunit to use. -if [[ -f "../vendor/bin/phpunit" ]]; then - phpunit_to_use="../vendor/bin/phpunit" -else - phpunit_to_use="phpunit" -fi - -phpunit_version="$("${phpunit_to_use}" --version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" -echo "phpunit_version: ${phpunit_version}" - -if [[ "${phpunit_version}" == "6.5."* ]]; then - phpunit_v6_5_shim -elif [[ "${phpunit_version}" == "7.5."* ]]; then - phpunit_v7_5_shim -elif [[ "${phpunit_version}" == "8.1."* ]]; then - phpunit_v8_1_shim -fi - -if [[ "${CI_PHP_VERSION}" == "7.0" ]]; then - php_v7_0_shim -fi - -# Run tests. -"${phpunit_to_use}" --version -"${phpunit_to_use}" \ - --configuration "phpunit.xml" \ - --debug \ - --verbose -if [[ "${?}" -ne 0 ]]; then - echo "Error: phpunit command failed" - errors+=("phpunit command failed") -fi - -source "check_coding_standards.sh" - -set +x - -error_count="${#errors[@]}" -if [[ "${error_count}" -ge 1 ]]; then - echo -e "\nErrors found: ${error_count}" - - iter=0 - for value in "${errors[@]}"; do - ((iter++)) - echo -e "\nError ${iter} of ${error_count}:" - echo "${value}" | perl -pe 's/^(.*)$/\t\1/' - done -fi - -# Stop test servers. -for pid in "${pids[@]}"; do - kill "${pid}" &> /dev/null & -done - -if [[ "${CI_PHP_FUTURE_RELEASE}" != "true" ]]; then - exit "${#errors[@]}" -elif [[ "${#errors[@]}" -ne 0 ]]; then - echo "One or more tests failed, but allowed as CI_PHP_FUTURE_RELEASE is on for PHP version ${CI_PHP_VERSION}." -fi diff --git a/tests/display_errors.inc.sh b/tests/display_errors.inc.sh new file mode 100644 index 0000000000..beec1d41ac --- /dev/null +++ b/tests/display_errors.inc.sh @@ -0,0 +1,11 @@ +error_count="${#errors[@]}" +if [[ "${error_count}" -ge 1 ]]; then + echo -e "\nErrors found: ${error_count}" + + iter=0 + for value in "${errors[@]}"; do + ((iter++)) + echo -e "\nError ${iter} of ${error_count}:" + echo "❌ ${value}" | perl -pe 's/^(.*)$/\t\1/' + done +fi diff --git a/tests/display_warnings.inc.sh b/tests/display_warnings.inc.sh new file mode 100644 index 0000000000..e145ca550d --- /dev/null +++ b/tests/display_warnings.inc.sh @@ -0,0 +1,11 @@ +warning_count="${#warnings[@]}" +if [[ "${warning_count}" -ge 1 ]]; then + echo -e "\nWarnings found: ${warning_count}" + + iter=0 + for value in "${warnings[@]}"; do + ((iter++)) + echo -e "\nWarning ${iter} of ${warning_count}:" + echo "⚠️ ${value}" | perl -pe 's/^(.*)$/\t\1/' + done +fi diff --git a/tests/dockerfiles/php70/1_build.sh b/tests/dockerfiles/php70/1_build.sh deleted file mode 100755 index 54f767bd76..0000000000 --- a/tests/dockerfiles/php70/1_build.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Build an image. -set -x -docker build --tag="php-curl-class/php70" . diff --git a/tests/dockerfiles/php70/2_start.sh b/tests/dockerfiles/php70/2_start.sh deleted file mode 100755 index 15d1e503dc..0000000000 --- a/tests/dockerfiles/php70/2_start.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Run image to create container. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker start "php70" || - docker run \ - --detach \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php70" \ - --tty \ - "php-curl-class/php70" diff --git a/tests/dockerfiles/php70/3_test.sh b/tests/dockerfiles/php70/3_test.sh deleted file mode 100755 index 6ba37b2425..0000000000 --- a/tests/dockerfiles/php70/3_test.sh +++ /dev/null @@ -1,17 +0,0 @@ -# Run tests inside container. -command=$(cat <<-END -mkdir --parents "/tmp/php-curl-class" && -rsync --delete --exclude=".git" --exclude="vendor" --exclude="composer.lock" --links --recursive "/data/" "/tmp/php-curl-class/" && -cd "/tmp/php-curl-class" && -export CI_PHP_VERSION="7.0" && -( - [ ! -f "/tmp/.composer_updated" ] && - composer --no-interaction update && - touch "/tmp/.composer_updated" || - exit 0 -) && -bash "tests/run.sh" -END -) -set -x -docker exec --tty "php70" sh -c "${command}" diff --git a/tests/dockerfiles/php70/4_stop.sh b/tests/dockerfiles/php70/4_stop.sh deleted file mode 100755 index 31563f4c7f..0000000000 --- a/tests/dockerfiles/php70/4_stop.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Stop container. -set -x -docker stop "php70" diff --git a/tests/dockerfiles/php70/Dockerfile b/tests/dockerfiles/php70/Dockerfile deleted file mode 100644 index 449fb18fb0..0000000000 --- a/tests/dockerfiles/php70/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM php:7.0-cli -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get --assume-yes --quiet update - -RUN apt-get --assume-yes --quiet install git && \ - apt-get --assume-yes --quiet install libpng-dev && \ - apt-get --assume-yes --quiet install zip - -RUN curl --silent --show-error "https://getcomposer.org/installer" | php && \ - mv "composer.phar" "/usr/local/bin/composer" && \ - composer global require --no-interaction "phpunit/phpunit" - -RUN docker-php-ext-configure gd && \ - docker-php-ext-install gd - -ENV PATH /root/.composer/vendor/bin:$PATH -CMD ["bash"] diff --git a/tests/dockerfiles/php70/attach.sh b/tests/dockerfiles/php70/attach.sh deleted file mode 100755 index 2a09b80a48..0000000000 --- a/tests/dockerfiles/php70/attach.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Attach to running container. - -docker exec --interactive --tty "php70" bash -l diff --git a/tests/dockerfiles/php71/1_build.sh b/tests/dockerfiles/php71/1_build.sh deleted file mode 100755 index f61a4bc850..0000000000 --- a/tests/dockerfiles/php71/1_build.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Build an image. -set -x -docker build --tag="php-curl-class/php71" . diff --git a/tests/dockerfiles/php71/2_start.sh b/tests/dockerfiles/php71/2_start.sh deleted file mode 100755 index 8b2ad9fbc2..0000000000 --- a/tests/dockerfiles/php71/2_start.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Run image to create container. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker start "php71" || - docker run \ - --detach \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php71" \ - --tty \ - "php-curl-class/php71" diff --git a/tests/dockerfiles/php71/4_stop.sh b/tests/dockerfiles/php71/4_stop.sh deleted file mode 100755 index 8a16db2c80..0000000000 --- a/tests/dockerfiles/php71/4_stop.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Stop container. -set -x -docker stop "php71" diff --git a/tests/dockerfiles/php71/Dockerfile b/tests/dockerfiles/php71/Dockerfile deleted file mode 100644 index 654d6f5601..0000000000 --- a/tests/dockerfiles/php71/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM php:7.1-cli -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get --assume-yes --quiet update - -RUN apt-get --assume-yes --quiet install git && \ - apt-get --assume-yes --quiet install libpng-dev && \ - apt-get --assume-yes --quiet install rsync && \ - apt-get --assume-yes --quiet install zip - -RUN curl --silent --show-error "https://getcomposer.org/installer" | php && \ - mv "composer.phar" "/usr/local/bin/composer" && \ - composer global require --no-interaction "phpunit/phpunit" - -RUN docker-php-ext-configure gd && \ - docker-php-ext-install gd - -ENV PATH /root/.composer/vendor/bin:$PATH -CMD ["bash"] diff --git a/tests/dockerfiles/php71/attach.sh b/tests/dockerfiles/php71/attach.sh deleted file mode 100755 index 2a3ae0fb3b..0000000000 --- a/tests/dockerfiles/php71/attach.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Attach to running container. - -docker exec --interactive --tty "php71" bash -l diff --git a/tests/dockerfiles/php71/run.sh b/tests/dockerfiles/php71/run.sh deleted file mode 100755 index ced63cf365..0000000000 --- a/tests/dockerfiles/php71/run.sh +++ /dev/null @@ -1,23 +0,0 @@ -bash "1_build.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Build failed" - exit 1 -fi - -bash "2_start.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Start failed" - exit 1 -fi - -bash "3_test.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Test failed" - exit 1 -fi - -bash "4_stop.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Stop failed" - exit 1 -fi diff --git a/tests/dockerfiles/php71/run_interactive.sh b/tests/dockerfiles/php71/run_interactive.sh deleted file mode 100755 index d0ec358b91..0000000000 --- a/tests/dockerfiles/php71/run_interactive.sh +++ /dev/null @@ -1,14 +0,0 @@ -# Run image to create container and attach to it. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker run \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php71" \ - --rm \ - --tty \ - "php-curl-class/php71" /bin/bash diff --git a/tests/dockerfiles/php72/1_build.sh b/tests/dockerfiles/php72/1_build.sh deleted file mode 100755 index 408cfad5ef..0000000000 --- a/tests/dockerfiles/php72/1_build.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Build an image. -set -x -docker build --tag="php-curl-class/php72" . diff --git a/tests/dockerfiles/php72/3_test.sh b/tests/dockerfiles/php72/3_test.sh deleted file mode 100755 index 3eec9f85d8..0000000000 --- a/tests/dockerfiles/php72/3_test.sh +++ /dev/null @@ -1,17 +0,0 @@ -# Run tests inside container. -command=$(cat <<-END -mkdir --parents "/tmp/php-curl-class" && -rsync --delete --exclude=".git" --exclude="vendor" --exclude="composer.lock" --links --recursive "/data/" "/tmp/php-curl-class/" && -cd "/tmp/php-curl-class" && -export CI_PHP_VERSION="7.2" && -( - [ ! -f "/tmp/.composer_updated" ] && - composer --no-interaction update && - touch "/tmp/.composer_updated" || - exit 0 -) && -bash "tests/run.sh" -END -) -set -x -docker exec --tty "php72" sh -c "${command}" diff --git a/tests/dockerfiles/php72/Dockerfile b/tests/dockerfiles/php72/Dockerfile deleted file mode 100644 index b3c6ecb5b0..0000000000 --- a/tests/dockerfiles/php72/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM php:7.2-cli -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get --assume-yes --quiet update - -RUN apt-get --assume-yes --quiet install git && \ - apt-get --assume-yes --quiet install libpng-dev && \ - apt-get --assume-yes --quiet install rsync && \ - apt-get --assume-yes --quiet install zip - -RUN curl --silent --show-error "https://getcomposer.org/installer" | php && \ - mv "composer.phar" "/usr/local/bin/composer" && \ - composer global require --no-interaction "phpunit/phpunit" - -RUN docker-php-ext-configure gd && \ - docker-php-ext-install gd - -ENV PATH /root/.composer/vendor/bin:$PATH -CMD ["bash"] diff --git a/tests/dockerfiles/php72/attach.sh b/tests/dockerfiles/php72/attach.sh deleted file mode 100755 index cc6d36efd5..0000000000 --- a/tests/dockerfiles/php72/attach.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Attach to running container. - -docker exec --interactive --tty "php72" bash -l diff --git a/tests/dockerfiles/php72/run.sh b/tests/dockerfiles/php72/run.sh deleted file mode 100755 index ced63cf365..0000000000 --- a/tests/dockerfiles/php72/run.sh +++ /dev/null @@ -1,23 +0,0 @@ -bash "1_build.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Build failed" - exit 1 -fi - -bash "2_start.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Start failed" - exit 1 -fi - -bash "3_test.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Test failed" - exit 1 -fi - -bash "4_stop.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Stop failed" - exit 1 -fi diff --git a/tests/dockerfiles/php72/run_interactive.sh b/tests/dockerfiles/php72/run_interactive.sh deleted file mode 100755 index df7fe12bab..0000000000 --- a/tests/dockerfiles/php72/run_interactive.sh +++ /dev/null @@ -1,14 +0,0 @@ -# Run image to create container and attach to it. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker run \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php72" \ - --rm \ - --tty \ - "php-curl-class/php72" /bin/bash diff --git a/tests/dockerfiles/php73/1_build.sh b/tests/dockerfiles/php73/1_build.sh deleted file mode 100755 index 4cb42d3cb4..0000000000 --- a/tests/dockerfiles/php73/1_build.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Build an image. -set -x -docker build --tag="php-curl-class/php73" . diff --git a/tests/dockerfiles/php73/2_start.sh b/tests/dockerfiles/php73/2_start.sh deleted file mode 100755 index de2a49bbff..0000000000 --- a/tests/dockerfiles/php73/2_start.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Run image to create container. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker start "php73" || - docker run \ - --detach \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php73" \ - --tty \ - "php-curl-class/php73" diff --git a/tests/dockerfiles/php73/3_test.sh b/tests/dockerfiles/php73/3_test.sh deleted file mode 100755 index 3ce53c2807..0000000000 --- a/tests/dockerfiles/php73/3_test.sh +++ /dev/null @@ -1,17 +0,0 @@ -# Run tests inside container. -command=$(cat <<-END -mkdir --parents "/tmp/php-curl-class" && -rsync --delete --exclude=".git" --exclude="vendor" --exclude="composer.lock" --links --recursive "/data/" "/tmp/php-curl-class/" && -cd "/tmp/php-curl-class" && -export CI_PHP_VERSION="7.3" && -( - [ ! -f "/tmp/.composer_updated" ] && - composer --no-interaction update && - touch "/tmp/.composer_updated" || - exit 0 -) && -bash "tests/run.sh" -END -) -set -x -docker exec --tty "php73" sh -c "${command}" diff --git a/tests/dockerfiles/php73/4_stop.sh b/tests/dockerfiles/php73/4_stop.sh deleted file mode 100755 index e119c63411..0000000000 --- a/tests/dockerfiles/php73/4_stop.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Stop container. -set -x -docker stop "php73" diff --git a/tests/dockerfiles/php73/attach.sh b/tests/dockerfiles/php73/attach.sh deleted file mode 100755 index f8ef94c496..0000000000 --- a/tests/dockerfiles/php73/attach.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Attach to running container. - -docker exec --interactive --tty "php73" bash -l diff --git a/tests/dockerfiles/php73/run.sh b/tests/dockerfiles/php73/run.sh deleted file mode 100755 index ced63cf365..0000000000 --- a/tests/dockerfiles/php73/run.sh +++ /dev/null @@ -1,23 +0,0 @@ -bash "1_build.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Build failed" - exit 1 -fi - -bash "2_start.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Start failed" - exit 1 -fi - -bash "3_test.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Test failed" - exit 1 -fi - -bash "4_stop.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Stop failed" - exit 1 -fi diff --git a/tests/dockerfiles/php73/run_interactive.sh b/tests/dockerfiles/php73/run_interactive.sh deleted file mode 100755 index b7410b551b..0000000000 --- a/tests/dockerfiles/php73/run_interactive.sh +++ /dev/null @@ -1,14 +0,0 @@ -# Run image to create container and attach to it. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker run \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php73" \ - --rm \ - --tty \ - "php-curl-class/php73" /bin/bash diff --git a/tests/dockerfiles/php74/1_build.sh b/tests/dockerfiles/php74/1_build.sh deleted file mode 100755 index 3bd1da9fa3..0000000000 --- a/tests/dockerfiles/php74/1_build.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Build an image. -set -x -docker build --tag="php-curl-class/php74" . diff --git a/tests/dockerfiles/php74/2_start.sh b/tests/dockerfiles/php74/2_start.sh deleted file mode 100755 index 516a97ad76..0000000000 --- a/tests/dockerfiles/php74/2_start.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Run image to create container. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker start "php74" || - docker run \ - --detach \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php74" \ - --tty \ - "php-curl-class/php74" diff --git a/tests/dockerfiles/php74/3_test.sh b/tests/dockerfiles/php74/3_test.sh deleted file mode 100755 index 549b52dd40..0000000000 --- a/tests/dockerfiles/php74/3_test.sh +++ /dev/null @@ -1,17 +0,0 @@ -# Run tests inside container. -command=$(cat <<-END -mkdir --parents "/tmp/php-curl-class" && -rsync --delete --exclude=".git" --exclude="vendor" --exclude="composer.lock" --links --recursive "/data/" "/tmp/php-curl-class/" && -cd "/tmp/php-curl-class" && -export CI_PHP_VERSION="7.4" && -( - [ ! -f "/tmp/.composer_updated" ] && - composer --no-interaction update && - touch "/tmp/.composer_updated" || - exit 0 -) && -bash "tests/run.sh" -END -) -set -x -docker exec --tty "php74" sh -c "${command}" diff --git a/tests/dockerfiles/php74/4_stop.sh b/tests/dockerfiles/php74/4_stop.sh deleted file mode 100755 index 6fb2e671ec..0000000000 --- a/tests/dockerfiles/php74/4_stop.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Stop container. -set -x -docker stop "php74" diff --git a/tests/dockerfiles/php74/Dockerfile b/tests/dockerfiles/php74/Dockerfile deleted file mode 100644 index 1efa876ce0..0000000000 --- a/tests/dockerfiles/php74/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM php:7.4-cli -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get --assume-yes --quiet update - -RUN apt-get --assume-yes --quiet install git && \ - apt-get --assume-yes --quiet install libpng-dev && \ - apt-get --assume-yes --quiet install rsync && \ - apt-get --assume-yes --quiet install zip - -RUN curl --silent --show-error "https://getcomposer.org/installer" | php && \ - mv "composer.phar" "/usr/local/bin/composer" && \ - composer global require --no-interaction "phpunit/phpunit" - -RUN docker-php-ext-configure gd && \ - docker-php-ext-install gd - -ENV PATH /root/.composer/vendor/bin:$PATH -CMD ["bash"] diff --git a/tests/dockerfiles/php74/attach.sh b/tests/dockerfiles/php74/attach.sh deleted file mode 100755 index 629773c984..0000000000 --- a/tests/dockerfiles/php74/attach.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Attach to running container. - -docker exec --interactive --tty "php74" bash -l diff --git a/tests/dockerfiles/php74/run.sh b/tests/dockerfiles/php74/run.sh deleted file mode 100755 index ced63cf365..0000000000 --- a/tests/dockerfiles/php74/run.sh +++ /dev/null @@ -1,23 +0,0 @@ -bash "1_build.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Build failed" - exit 1 -fi - -bash "2_start.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Start failed" - exit 1 -fi - -bash "3_test.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Test failed" - exit 1 -fi - -bash "4_stop.sh" -if [[ $? -ne 0 ]]; then - echo "Error: Stop failed" - exit 1 -fi diff --git a/tests/dockerfiles/php74/run_interactive.sh b/tests/dockerfiles/php74/run_interactive.sh deleted file mode 100755 index 3bc0c78289..0000000000 --- a/tests/dockerfiles/php74/run_interactive.sh +++ /dev/null @@ -1,14 +0,0 @@ -# Run image to create container and attach to it. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -set -x -cd "${SCRIPT_DIR}/../../.." -project_dir="${PWD}" - -docker run \ - --interactive \ - --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php74" \ - --rm \ - --tty \ - "php-curl-class/php74" /bin/bash diff --git a/tests/dockerfiles/php81/1_build.sh b/tests/dockerfiles/php81/1_build.sh new file mode 100755 index 0000000000..9c0fe703e5 --- /dev/null +++ b/tests/dockerfiles/php81/1_build.sh @@ -0,0 +1,3 @@ +# Build an image. +set -x +docker build --tag="php-curl-class/php81" . diff --git a/tests/dockerfiles/php72/2_start.sh b/tests/dockerfiles/php81/2_start.sh similarity index 78% rename from tests/dockerfiles/php72/2_start.sh rename to tests/dockerfiles/php81/2_start.sh index af24a69cd5..1eeea23a5f 100755 --- a/tests/dockerfiles/php72/2_start.sh +++ b/tests/dockerfiles/php81/2_start.sh @@ -5,11 +5,11 @@ set -x cd "${SCRIPT_DIR}/../../.." project_dir="${PWD}" -docker start "php72" || +docker start "php81" || docker run \ --detach \ --interactive \ --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php72" \ + --name="php81" \ --tty \ - "php-curl-class/php72" + "php-curl-class/php81" diff --git a/tests/dockerfiles/php71/3_test.sh b/tests/dockerfiles/php81/3_test.sh similarity index 84% rename from tests/dockerfiles/php71/3_test.sh rename to tests/dockerfiles/php81/3_test.sh index 181c9206f5..87f2d0bbac 100755 --- a/tests/dockerfiles/php71/3_test.sh +++ b/tests/dockerfiles/php81/3_test.sh @@ -3,7 +3,7 @@ command=$(cat <<-END mkdir --parents "/tmp/php-curl-class" && rsync --delete --exclude=".git" --exclude="vendor" --exclude="composer.lock" --links --recursive "/data/" "/tmp/php-curl-class/" && cd "/tmp/php-curl-class" && -export CI_PHP_VERSION="7.1" && +export CI_PHP_VERSION="8.1" && ( [ ! -f "/tmp/.composer_updated" ] && composer --no-interaction update && @@ -14,4 +14,4 @@ bash "tests/run.sh" END ) set -x -docker exec --tty "php71" sh -c "${command}" +docker exec --tty "php81" sh -c "${command}" diff --git a/tests/dockerfiles/php72/4_stop.sh b/tests/dockerfiles/php81/4_stop.sh similarity index 55% rename from tests/dockerfiles/php72/4_stop.sh rename to tests/dockerfiles/php81/4_stop.sh index da8f31ab84..b1b8959234 100755 --- a/tests/dockerfiles/php72/4_stop.sh +++ b/tests/dockerfiles/php81/4_stop.sh @@ -1,3 +1,3 @@ # Stop container. set -x -docker stop "php72" +docker stop "php81" diff --git a/tests/dockerfiles/php73/Dockerfile b/tests/dockerfiles/php81/Dockerfile similarity index 97% rename from tests/dockerfiles/php73/Dockerfile rename to tests/dockerfiles/php81/Dockerfile index ab796d70f8..d6b4ed9a9d 100644 --- a/tests/dockerfiles/php73/Dockerfile +++ b/tests/dockerfiles/php81/Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.3-cli +FROM php:8.1-cli ENV DEBIAN_FRONTEND noninteractive RUN apt-get --assume-yes --quiet update diff --git a/tests/dockerfiles/php81/attach.sh b/tests/dockerfiles/php81/attach.sh new file mode 100755 index 0000000000..8211a8524a --- /dev/null +++ b/tests/dockerfiles/php81/attach.sh @@ -0,0 +1,3 @@ +# Attach to running container. + +docker exec --interactive --tty "php81" bash -l diff --git a/tests/dockerfiles/php70/run.sh b/tests/dockerfiles/php81/run.sh similarity index 100% rename from tests/dockerfiles/php70/run.sh rename to tests/dockerfiles/php81/run.sh diff --git a/tests/dockerfiles/php70/run_interactive.sh b/tests/dockerfiles/php81/run_interactive.sh similarity index 83% rename from tests/dockerfiles/php70/run_interactive.sh rename to tests/dockerfiles/php81/run_interactive.sh index a089e63516..dee76fb6c6 100755 --- a/tests/dockerfiles/php70/run_interactive.sh +++ b/tests/dockerfiles/php81/run_interactive.sh @@ -8,7 +8,7 @@ project_dir="${PWD}" docker run \ --interactive \ --mount "type=bind,src=${project_dir},dst=/data,readonly=true" \ - --name="php70" \ + --name="php81" \ --rm \ --tty \ - "php-curl-class/php70" /bin/bash + "php-curl-class/php81" /bin/bash diff --git a/tests/generate_urls.py b/tests/generate_urls.py index ac4dc5d250..a6b415f217 100644 --- a/tests/generate_urls.py +++ b/tests/generate_urls.py @@ -1,17 +1,8 @@ -from itertools import product - -try: - from urllib.parse import urljoin -except ImportError: - from urlparse import urljoin - -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse - import csv import posixpath +from itertools import product +from urllib.parse import urljoin +from urllib.parse import urlparse def remove_dot_segments(url): @@ -24,119 +15,123 @@ def remove_dot_segments(url): parsed = urlparse(url) new_path = posixpath.normpath(parsed.path) - if parsed.path.endswith('/'): + if parsed.path.endswith("/"): # Fix missing trailing slash. # https://bugs.python.org/issue1707768 - new_path += '/' - if new_path.startswith('//'): + new_path += "/" + if new_path.startswith("//"): new_path = new_path[1:] cleaned = parsed._replace(path=new_path) return cleaned.geturl() first_authorities = [ - 'http://example.com@user:pass:7152', - 'https://example.com/', + "http://example.com@user:pass:7152", + "https://example.com/", ] second_authorities = [ - '', - 'https://www.example.org/', - 'http://example.com@user:pass:1111', - 'file://example.com', - 'file://', + "", + "https://www.example.org/", + "http://example.com@user:pass:1111", + "file://example.com", + "file://", ] first_paths = [ - '', - '/', - '/foobar/bazz', - 'foobar/bazz/', + "", + "/", + "/foobar/bazz", + "foobar/bazz/", ] second_paths = [ - '', - '/', - '/foo/bar', - 'foo/bar/', - './foo/../bar', - 'foo/./.././bar', + "", + "/", + "/foo/bar", + "foo/bar/", + "./foo/../bar", + "foo/./.././bar", ] -first_queries = ['', '?a=1', '?a=647&b=s564'] -second_queries = ['', '?a=sdf', '?a=cvb&b=987'] -fragments = ['', '#foo', '#bar'] +first_queries = ["", "?a=1", "?a=647&b=s564"] +second_queries = ["", "?a=sdf", "?a=cvb&b=987"] +fragments = ["", "#foo", "#bar"] additional_tests = [ { - 'args': [ - 'http://www.example.com/', - '', + "args": [ + "http://www.example.com/", + "", ], - 'expected': 'http://www.example.com/', + "expected": "http://www.example.com/", }, { - 'args': [ - 'http://www.example.com/', - 'foo', + "args": [ + "http://www.example.com/", + "foo", ], - 'expected': 'http://www.example.com/foo', + "expected": "http://www.example.com/foo", }, { - 'args': [ - 'http://www.example.com/', - '/foo', + "args": [ + "http://www.example.com/", + "/foo", ], - 'expected': 'http://www.example.com/foo', + "expected": "http://www.example.com/foo", }, { - 'args': [ - 'http://www.example.com/', - '/foo/', + "args": [ + "http://www.example.com/", + "/foo/", ], - 'expected': 'http://www.example.com/foo/', + "expected": "http://www.example.com/foo/", }, { - 'args': [ - 'http://www.example.com/', - '/dir/page.html', + "args": [ + "http://www.example.com/", + "/dir/page.html", ], - 'expected': 'http://www.example.com/dir/page.html', + "expected": "http://www.example.com/dir/page.html", }, { - 'args': [ - 'http://www.example.com/dir1/page2.html', - '/dir/page.html', + "args": [ + "http://www.example.com/dir1/page2.html", + "/dir/page.html", ], - 'expected': 'http://www.example.com/dir/page.html', + "expected": "http://www.example.com/dir/page.html", }, { - 'args': [ - 'http://www.example.com/dir1/page2.html', - 'dir/page.html', + "args": [ + "http://www.example.com/dir1/page2.html", + "dir/page.html", ], - 'expected': 'http://www.example.com/dir1/dir/page.html', + "expected": "http://www.example.com/dir1/dir/page.html", }, { - 'args': [ - 'http://www.example.com/dir1/dir3/page.html', - '../dir/page.html', + "args": [ + "http://www.example.com/dir1/dir3/page.html", + "../dir/page.html", ], - 'expected': 'http://www.example.com/dir1/dir/page.html', + "expected": "http://www.example.com/dir1/dir/page.html", }, ] -with open('urls.csv', 'wt') as f: +with open("urls.csv", "wt") as f: csvwriter = csv.writer(f, quotechar='"', quoting=csv.QUOTE_ALL) - csvwriter.writerow(['first_url', 'second_url', 'expected']) + csvwriter.writerow(["first_url", "second_url", "expected"]) for test in additional_tests: - csvwriter.writerow([test['args'][0], test['args'][1], test['expected']]) + csvwriter.writerow([test["args"][0], test["args"][1], test["expected"]]) for first_domain, second_domain in product(first_authorities, second_authorities): for first_path, second_path in product(first_paths, second_paths): for first_query, second_query in product(first_queries, second_queries): for first_fragment, second_fragment in product(fragments, fragments): - if not first_path.startswith('/'): - first_path = '/' + first_path + if not first_path.startswith("/"): + first_path = "/" + first_path first_url = first_domain + first_path + first_query + first_fragment - if second_domain and not second_path.startswith('/'): - second_path = '/' + second_path - second_url = second_domain + second_path + second_query + second_fragment + if second_domain and not second_path.startswith("/"): + second_path = "/" + second_path + second_url = ( + second_domain + second_path + second_query + second_fragment + ) if first_url != second_url: - expected_url = remove_dot_segments(urljoin(first_url, second_url)) + expected_url = remove_dot_segments( + urljoin(first_url, second_url) + ) csvwriter.writerow([first_url, second_url, expected_url]) diff --git a/tests/generate_urls.sh b/tests/generate_urls.sh index 175f148136..cfb11d3f09 100755 --- a/tests/generate_urls.sh +++ b/tests/generate_urls.sh @@ -3,7 +3,7 @@ cd "${SCRIPT_DIR}" set -x -python "generate_urls.py" && +python3 "generate_urls.py" && ([[ -f "urls.csv.gz" ]] && rm --verbose "urls.csv.gz" || exit 0) && gzip --verbose --best --no-name "urls.csv" && gzip --verbose --test "urls.csv.gz" && diff --git a/tests/PHPCurlClass/index.php b/tests/index.php similarity index 100% rename from tests/PHPCurlClass/index.php rename to tests/index.php diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon new file mode 100644 index 0000000000..cde552e0fc --- /dev/null +++ b/tests/phpstan-baseline.neon @@ -0,0 +1,13 @@ +parameters: + ignoreErrors: + - + message: '#^Class CurlHandle not found\.$#' + identifier: class.notFound + count: 2 + path: ../src/Curl/Curl.php + + - + message: '#^Class CurlMultiHandle not found\.$#' + identifier: class.notFound + count: 2 + path: ../src/Curl/MultiCurl.php diff --git a/tests/phpstan.neon b/tests/phpstan.neon new file mode 100644 index 0000000000..d933374d82 --- /dev/null +++ b/tests/phpstan.neon @@ -0,0 +1,16 @@ +includes: + - phpstan-baseline.neon + - phar://phpstan.phar/conf/bleedingEdge.neon + +parameters: + reportUnmatchedIgnoredErrors: false + + # TODO: Increase rule level to be more strict. + level: 3 + + # TODO: Remove all exclusions except vendor/ and fix related errors. + excludePaths: + - ../examples/* + - ../tests/* + - ../vendor/* + - ../www/* diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 87b3ee161e..ea668de18c 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,7 +1,7 @@ - ./PHPCurlClass/ + ../tests/ diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml new file mode 100644 index 0000000000..9515635b16 --- /dev/null +++ b/tests/psalm-baseline.xml @@ -0,0 +1,25 @@ + + + + + + + + + curl]]> + + + + + + + + + multiCurl]]> + multiCurl]]> + multiCurl]]> + multiCurl]]> + multiCurl]]> + + + diff --git a/tests/psalm.xml b/tests/psalm.xml new file mode 100644 index 0000000000..2a56ee25db --- /dev/null +++ b/tests/psalm.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ruleset.xml b/tests/ruleset.xml index cc281a9442..10ddbed48f 100644 --- a/tests/ruleset.xml +++ b/tests/ruleset.xml @@ -1,18 +1,32 @@ - + - - + + + + + - - + + + + + - + + + + + ./examples/* + - - + + + + + diff --git a/tests/run.sh b/tests/run.sh index 74e348ea56..e0b843252d 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,127 +1,66 @@ -remove_expectWarning() { - # Fix "Call to undefined method CurlTest\CurlTest::expectWarning()". - sed -i'' -e"/->expectWarning(/d" "./PHPCurlClass/PHP"* -} - -replace_assertStringContainsString() { - # -->assertStringContainsString( - # +->assertContains( - find='->assertStringContainsString(' - replace='->assertContains(' - sed -i'' -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* -} - -phpunit_v6_5_shim() { - remove_expectWarning - replace_assertStringContainsString -} - -phpunit_v7_5_shim() { - remove_expectWarning -} - -phpunit_v8_1_shim() { - remove_expectWarning -} - -php_v7_0_shim() { - # -protected function setUp(): void - # +protected function setUp() - find='protected function setUp(): void' - replace='protected function setUp()' - sed -i'' -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* -} +#!/usr/bin/env bash set -x +warnings=() +errors=() + +if [[ "${CI}" == "true" ]]; then + composer self-update + + # Skip attempting to install psalm on future PHP releases to avoid the + # following error that would otherwise block the remaining tests from + # running: + # Your requirements could not be resolved to an installable set of packages. + # + # Problem 1 + # - Root composer.json requires vimeo/psalm >=5.26.1 -> satisfiable by vimeo/psalm[5.26.1, 6.0.0, ..., 6.8.8]. + # - vimeo/psalm 5.26.1 requires php ^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 -> your php version (8.5.0-dev) does not satisfy that requirement. + # - vimeo/psalm[6.0.0, ..., 6.5.0] require php ~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0 -> your php version (8.5.0-dev) does not satisfy that requirement. + # - vimeo/psalm[6.5.1, ..., 6.8.8] require php ~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 -> your php version (8.5.0-dev) does not satisfy that requirement. + # + # Error: Your requirements could not be resolved to an installable set of packages. + if ! "${CI_PHP_FUTURE_RELEASE}"; then + composer require --dev "vimeo/psalm:>=5.26.1" + fi + + composer install --prefer-source --no-interaction + if [[ "${?}" -ne 0 ]]; then + echo "❌ Error: composer install failed" + errors+=("composer install failed") + fi +fi + # Use composer's phpunit and phpcs by adding composer bin directory to the path environment variable. export PATH="${PWD}/vendor/bin:${PATH}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +source "set_vars.inc.sh" + echo "CI_PHP_VERSION: ${CI_PHP_VERSION}" echo "CI_PHP_FUTURE_RELEASE: ${CI_PHP_FUTURE_RELEASE}" php -r "var_dump(phpversion());" php -r "var_dump(curl_version());" -# Let test server know we should allow testing. -export PHP_CURL_CLASS_TEST_MODE_ENABLED="yes" +source "run_syntax_check.sh" -# Start test servers. Run servers on different ports to allow simultaneous -# requests without blocking. -server_count=7 -declare -A pids -for i in $(seq 0 $(("${server_count}" - 1))); do - port=8000 - (( port += $i )) +source "run_coding_standards_check.sh" - php -S "127.0.0.1:${port}" -t PHPCurlClass/ &> /dev/null & - pids["${i}"]="${!}" -done +source "run_phpunit.sh" -errors=() - -source "check_syntax.sh" +source "run_static_analysis_check_phpstan.sh" -# Determine which phpunit to use. -if [[ -f "../vendor/bin/phpunit" ]]; then - phpunit_to_use="../vendor/bin/phpunit" -else - phpunit_to_use="phpunit" -fi - -phpunit_version="$("${phpunit_to_use}" --version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" -echo "phpunit_version: ${phpunit_version}" - -if [[ "${phpunit_version}" == "6.5."* ]]; then - phpunit_v6_5_shim -elif [[ "${phpunit_version}" == "7.5."* ]]; then - phpunit_v7_5_shim -elif [[ "${phpunit_version}" == "8.1."* ]]; then - phpunit_v8_1_shim -fi - -if [[ "${CI_PHP_VERSION}" == "7.0" ]]; then - php_v7_0_shim -fi - -# Run tests. -extra_args="${@}" -"${phpunit_to_use}" --version -"${phpunit_to_use}" \ - --configuration "phpunit.xml" \ - --debug \ - --verbose \ - ${extra_args} -if [[ "${?}" -ne 0 ]]; then - echo "Error: phpunit command failed" - errors+=("phpunit command failed") -fi - -source "check_coding_standards.sh" +source "run_static_analysis_check_psalm.sh" set +x -error_count="${#errors[@]}" -if [[ "${error_count}" -ge 1 ]]; then - echo -e "\nErrors found: ${error_count}" - - iter=0 - for value in "${errors[@]}"; do - ((iter++)) - echo -e "\nError ${iter} of ${error_count}:" - echo "${value}" | perl -pe 's/^(.*)$/\t\1/' - done -fi - -# Stop test servers. -for pid in "${pids[@]}"; do - kill "${pid}" &> /dev/null & -done +source "display_warnings.inc.sh" +source "display_errors.inc.sh" if [[ "${CI_PHP_FUTURE_RELEASE}" != "true" ]]; then exit "${#errors[@]}" elif [[ "${#errors[@]}" -ne 0 ]]; then - echo "One or more tests failed, but allowed as CI_PHP_FUTURE_RELEASE is on for PHP version ${CI_PHP_VERSION}." + echo "⚠️ One or more tests failed, but allowed as the CI_PHP_FUTURE_RELEASE flag is on for PHP version ${CI_PHP_VERSION}." fi diff --git a/tests/check_coding_standards.sh b/tests/run_coding_standards_check.sh similarity index 62% rename from tests/check_coding_standards.sh rename to tests/run_coding_standards_check.sh index a830ef0394..7fdcde9925 100755 --- a/tests/check_coding_standards.sh +++ b/tests/run_coding_standards_check.sh @@ -1,14 +1,18 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +source "set_vars.inc.sh" + # Run commands from the project root directory. -cd .. +pushd .. # Enforce line ending consistency in php files. crlf_file=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --files-with-matches $'\r' {} \;) if [[ ! -z "${crlf_file}" ]]; then result="$(echo "${crlf_file}" | perl -pe 's/(.*)/CRLF line terminators found in \1/')" - echo "${result}" + echo "❌ ${result}" errors+=("${result}") fi @@ -16,7 +20,7 @@ fi tab_char=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --line-number -H --perl-regexp "\t" {} \;) if [[ ! -z "${tab_char}" ]]; then result="$(echo -e "${tab_char}" | perl -pe 's/^(.*)$/Tab character found in \1/')" - echo "${result}" + echo "❌ ${result}" errors+=("${result}") fi @@ -64,55 +68,23 @@ EOF export -f "find_invalid_indentation" invalid_indentation=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec bash -c 'find_invalid_indentation "{}"' \;) if [[ ! -z "${invalid_indentation}" ]]; then - echo "${invalid_indentation}" + echo "❌ ${invalid_indentation}" errors+=("${invalid_indentation}") fi # Prohibit trailing whitespace in php files. -trailing_whitespace=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec egrep --color=always --line-number -H " +$" {} \;) +trailing_whitespace=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --extended-regexp --line-number -H " +$" {} \;) if [[ ! -z "${trailing_whitespace}" ]]; then result="$(echo -e "${trailing_whitespace}" | perl -pe 's/^(.*)$/Trailing whitespace found in \1/')" - echo "${result}" - errors+=("${result}") -fi - -# Prohibit long lines in php files. -long_lines=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" ! -path "*/www/*" -exec awk '{print FILENAME":"NR" "length}' {} \; | awk '$2 > 120') -if [[ ! -z "${long_lines}" ]]; then - result="$(echo -e "${long_lines}" | perl -pe 's/^(.*)$/Long lines found in \1/')" - echo "${result}" - errors+=("${result}") -fi - -# Prohibit @author in php files. -at_author=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec egrep --color=always --line-number -H "@author" {} \;) -if [[ ! -z "${at_author}" ]]; then - result="$(echo -e "${at_author}" | perl -pe 's/^(.*)$/\@author found in \1/')" - echo "${result}" - errors+=("${result}") -fi - -# Prohibit screaming caps notation in php files. -caps=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec egrep --color=always --line-number -H -e "FALSE[^']" -e "NULL" -e "TRUE" {} \;) -if [[ ! -z "${caps}" ]]; then - result="$(echo -e "${caps}" | perl -pe 's/^(.*)$/All caps found in \1/')" - echo "${result}" + echo "❌ ${result}" errors+=("${result}") fi # Require identical comparison operators (===, not ==) in php files. -equal=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec egrep --color=always --line-number -H "[^!=]==[^=]" {} \;) +equal=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --extended-regexp --line-number -H "[^!=]==[^=]" {} \;) if [[ ! -z "${equal}" ]]; then result="$(echo -e "${equal}" | perl -pe 's/^(.*)$/Non-identical comparison operator found in \1/')" - echo "${result}" - errors+=("${result}") -fi - -# Require keyword "elseif" to be used instead of "else if" so that all control keywords look like single words. -elseif=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec egrep --color=always --line-number -H "else\s+if" {} \;) -if [[ ! -z "${elseif}" ]]; then - result="$(echo -e "${elseif}" | perl -pe 's/^(.*)$/Found "else if" instead of "elseif" in \1/')" - echo "${result}" + echo "❌ ${result}" errors+=("${result}") fi @@ -120,18 +92,11 @@ fi elses=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --line-number -H --perl-regexp '^(\s+)?else(\s+)?{' {} \;) if [[ ! -z "${elses}" ]]; then result="$(echo -e "${elses}" | perl -pe 's/^(.*)$/Found newline before "else" statement in \1/')" - echo "${result}" - errors+=("${result}") -fi - -# Prohibit use of "is_null" and suggest using the strict comparison operator. -is_null=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --line-number -H -e "is_null" {} \;) -if [[ ! -z "${is_null}" ]]; then - result="$(echo -e "${is_null}" | perl -pe 's/^(.*)$/is_null found in \1. Replace with strict comparison (e.g. "\$x === null")./')" - echo "${result}" + echo "❌ ${result}" errors+=("${result}") fi +# Run PHP_CodeSniffer. # Determine which phpcs to use. if [[ -f "vendor/bin/phpcs" ]]; then phpcs_to_use="vendor/bin/phpcs" @@ -149,6 +114,16 @@ fi -s \ . if [[ "${?}" -ne 0 ]]; then - echo "Error: found standard violation(s)" - errors+=("found standard violation(s)") + echo "❌ Error: found PHP_CodeSniffer coding standard violation(s)" + errors+=("found PHP_CodeSniffer coding standard violation(s)") fi + +# Run PHP-CS-Fixer. +vendor/bin/php-cs-fixer --version +vendor/bin/php-cs-fixer fix --ansi --config="tests/.php-cs-fixer.php" --diff --dry-run +if [[ "${?}" -ne 0 ]]; then + echo "❌ Error: found PHP-CS-Fixer coding standard violation(s)" + errors+=("found PHP-CS-Fixer coding standard violation(s)") +fi + +popd diff --git a/tests/run_phpunit.sh b/tests/run_phpunit.sh new file mode 100755 index 0000000000..46d1e1c037 --- /dev/null +++ b/tests/run_phpunit.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash + +remove_expectWarning() { + # Fix "Call to undefined method CurlTest\CurlTest::expectWarning()". + if sed v < /dev/null 2> /dev/null; then + sed -i"" -e "/->expectWarning(/d" "./PHPCurlClass/PHP"* + else + sed -i "" -e "/->expectWarning(/d" "./PHPCurlClass/PHP"* + fi +} + +replace_assertStringContainsString() { + # -->assertStringContainsString( + # +->assertContains( + find='->assertStringContainsString(' + replace='->assertContains(' + if sed v < /dev/null 2> /dev/null; then + sed -i"" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* + else + sed -i "" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* + fi +} + +replace_assertMatchesRegularExpression() { + # -->assertMatchesRegularExpression( + # +->assertRegExp( + find='->assertMatchesRegularExpression(' + replace='->assertRegExp(' + if sed v < /dev/null 2> /dev/null; then + sed -i"" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* + else + sed -i "" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* + fi +} + +phpunit_v6_5_shim() { + remove_expectWarning + replace_assertMatchesRegularExpression + replace_assertStringContainsString +} + +phpunit_v7_5_shim() { + remove_expectWarning + replace_assertMatchesRegularExpression +} + +phpunit_v8_5_shim() { + remove_expectWarning + replace_assertMatchesRegularExpression +} + +phpunit_v9_shim() { + replace_assertMatchesRegularExpression +} + +phpunit_v10_shim() { + remove_expectWarning +} + +phpunit_v11_shim() { + :; +} + +phpunit_v12_shim() { + remove_expectWarning +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +# Let test server know we should allow testing. +export PHP_CURL_CLASS_TEST_MODE_ENABLED="yes" + +# Start test servers. Run servers on different ports to allow simultaneous +# requests without blocking. +server_count=7 +pids=() +for i in $(seq 0 "$(echo "${server_count} - 1" | bc)"); do + port=8000 + (( port += $i )) + + php -S "127.0.0.1:${port}" server.php &> /dev/null & + pids+=("${!}") +done + +# Determine which phpunit to use. +if [[ -f "../vendor/bin/phpunit" ]]; then + phpunit_to_use="../vendor/bin/phpunit" +else + phpunit_to_use="phpunit" +fi + +phpunit_version="$("${phpunit_to_use}" --version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" +echo "phpunit_version: ${phpunit_version}" + +extra_args="${@}" +if [[ "${phpunit_version}" == "6.5."* ]]; then + phpunit_v6_5_shim + phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "7.5."* ]]; then + phpunit_v7_5_shim + phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "8.5."* ]]; then + phpunit_v8_5_shim + phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "9."* ]]; then + phpunit_v9_shim + phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "10."* ]]; then + phpunit_v10_shim + phpunit_args=" --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "11."* ]]; then + phpunit_v11_shim + phpunit_args=" --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings --fail-on-risky ${extra_args}" +elif [[ "${phpunit_version}" == "12."* ]]; then + phpunit_v12_shim + phpunit_args=" --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings --fail-on-risky ${extra_args}" +fi + +# Run tests. +"${phpunit_to_use}" --version +"${phpunit_to_use}" \ + --configuration "phpunit.xml" \ + ${phpunit_args} +if [[ "${?}" -ne 0 ]]; then + echo "❌ Error: phpunit command failed" + errors+=("phpunit command failed") +fi + +# Stop test servers. +for pid in "${pids[@]}"; do + kill "${pid}" &> /dev/null & +done diff --git a/tests/run_static_analysis_check_phpstan.sh b/tests/run_static_analysis_check_phpstan.sh new file mode 100755 index 0000000000..970a71d53f --- /dev/null +++ b/tests/run_static_analysis_check_phpstan.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +source "set_vars.inc.sh" + +# Run commands from the project root directory. +pushd .. + +set -x + +if true; then + + phpstan_args=(--ansi --configuration="tests/phpstan.neon") + + # Increase memory limit on local development. + if [[ "${CI}" != "true" ]]; then + phpstan_args+=(--memory-limit=256M) + fi + + vendor/bin/phpstan --version + vendor/bin/phpstan analyse "${phpstan_args[@]}" . + if [[ "${?}" -ne 0 ]]; then + echo "❌ Error: phpstan static analysis check failed" + errors+=("phpstan static analysis check failed") + fi +else + echo "⚠️ Skipped running phpstan check" + warnings+=("Skipped running phpstan check") +fi + +popd diff --git a/tests/run_static_analysis_check_psalm.sh b/tests/run_static_analysis_check_psalm.sh new file mode 100755 index 0000000000..2ed25a28ef --- /dev/null +++ b/tests/run_static_analysis_check_psalm.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +source "set_vars.inc.sh" + +# Run commands from the project root directory. +pushd .. + +set -x + +if [[ ! -f "vendor/bin/psalm" ]]; then + echo "⚠️ Skipped running psalm static analysis check" + warnings+=("Skipped running psalm static analysis check") +else + vendor/bin/psalm --version + vendor/bin/psalm --config="tests/psalm.xml" + + if [[ "${?}" -ne 0 ]]; then + echo "❌ Error: psalm static analysis check failed" + errors+=("psalm static analysis check failed") + fi +fi + +popd diff --git a/tests/check_syntax.sh b/tests/run_syntax_check.sh similarity index 56% rename from tests/check_syntax.sh rename to tests/run_syntax_check.sh index 223cb522f4..01a98868ab 100755 --- a/tests/check_syntax.sh +++ b/tests/run_syntax_check.sh @@ -1,9 +1,16 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +# Run commands from the project root directory. +pushd .. + # Check syntax in php files. Use `xargs' over `find -exec' as xargs exits with a value of 1 when any command errors. -find .. -type "f" -iname "*.php" ! -path "*/vendor/*" | xargs -L "1" php -l +find . -type "f" -iname "*.php" ! -path "*/vendor/*" | xargs -L "1" php -l if [[ "${?}" -ne 0 ]]; then - echo "Error: php syntax checks failed" + echo "❌ Error: php syntax checks failed" errors+=("php syntax checks failed") fi + +popd diff --git a/tests/PHPCurlClass/server.php b/tests/server.php similarity index 76% rename from tests/PHPCurlClass/server.php rename to tests/server.php index 5abfc9d7ff..c7a701e9ee 100644 --- a/tests/PHPCurlClass/server.php +++ b/tests/server.php @@ -1,9 +1,14 @@ - null, + 'true' => true, + 'false' => false, + 'integer' => 1, + 'float' => 3.14, + 'empty' => '', + 'string' => 'string', + ]); } - echo json_encode([ - 'null' => null, - 'true' => true, - 'false' => false, - 'integer' => 1, - 'float' => 3.14, - 'empty' => '', - 'string' => 'string', - ]); + + if (isset($_POST['remove-content-type-header'])) { + header_remove('Content-Type'); + } + + echo $body; exit; } elseif ($test === 'xml_response') { $key = $_POST['key']; @@ -258,15 +288,19 @@ $unsafe_file_path = $_GET['file_path']; header('Content-Type: image/png'); header('Content-Disposition: attachment; filename="image.png"'); - header('Content-Length: ' . filesize($unsafe_file_path)); - header('ETag: ' . md5_file($unsafe_file_path)); - readfile($unsafe_file_path); + + if (!isset($_SERVER['HTTP_RANGE'])) { + header('ETag: ' . md5_file($unsafe_file_path)); + } + + $server = new ContentRangeServer\ContentRangeServer(); + $server->serve($unsafe_file_path); exit; } elseif ($test === 'download_file_size') { if (isset($_GET['http_response_code'])) { http_response_code((int) $_GET['http_response_code']); } - $bytes = isset($_GET['bytes']) ? $_GET['bytes'] : 1234; + $bytes = $_GET['bytes'] ?? 1234; $str = str_repeat('.', (int) $bytes); header('Content-Type: application/octet-stream'); header('Content-Length: ' . strlen($str)); @@ -352,25 +386,50 @@ } elseif ($test === 'retry') { session_start(); - if (isset($_SESSION['failures_remaining'])) { - $failures_remaining = $_SESSION['failures_remaining']; + if (isset($_SESSION['should_fail_entries'])) { + $should_fail_entries = $_SESSION['should_fail_entries']; } else { - $failures_remaining = (int)$_GET['failures']; - $_SESSION['failures_remaining'] = $failures_remaining; + // Support specifying which requests fail and succeed (e.g. + // http://127.0.0.1:8000/?failures=1,1,0 to fail, fail, succeed). + if (strpos($_GET['failures'], ',') !== false) { + $should_fail_entries = explode(',', $_GET['failures']); + array_walk($should_fail_entries, function (&$value, $key) { + if ($value === '1') { + $value = true; + } elseif ($value === '0') { + $value = false; + } else { + $value = ''; + } + }); + + // Support specifying the number of failures before a success (e.g. + // http://127.0.0.1:8000/?failures=3). + } else { + $failure_count = (int)$_GET['failures']; + $should_fail_entries = array_fill(0, $failure_count, true); + $should_fail_entries[] = false; + } } - if ($failures_remaining >= 1) { - $_SESSION['failures_remaining'] -= 1; + $should_fail = array_shift($should_fail_entries); + $_SESSION['should_fail_entries'] = $should_fail_entries; - header('HTTP/1.1 503 Service Unavailable'); - echo 'Service Unavailable'; - echo ' (remaining failures: ' . $_SESSION['failures_remaining'] . ')'; - exit; + if ($should_fail) { + $message = '503 Service Unavailable'; + } else { + $message = '202 Accepted'; } - header('HTTP/1.1 202 Accepted'); - echo '202 Accepted'; - echo ' (remaining failures: ' . $_SESSION['failures_remaining'] . ')'; + $response = json_encode([ + 'message' => $message, + 'remaining_should_fail_entries' => $_SESSION['should_fail_entries'], + ], JSON_PRETTY_PRINT); + + header('HTTP/1.1 ' . $message); + header('Content-Type: application/json'); + header('Content-Length: ' . strlen($response)); + echo $response; exit; } elseif ($test === '404') { header('HTTP/1.1 404 Not Found'); @@ -397,7 +456,7 @@ $value = http_build_query($data); } else { // Return individual value when a key is specified. - $value = isset($data[$key]) ? $data[$key] : ''; + $value = $data[$key] ?? ''; } echo $value; } diff --git a/tests/set_vars.inc.sh b/tests/set_vars.inc.sh new file mode 100644 index 0000000000..dc33f7b01e --- /dev/null +++ b/tests/set_vars.inc.sh @@ -0,0 +1,4 @@ +# Use installed version when variable not set. +if [[ -z "${CI_PHP_VERSION}" ]]; then + CI_PHP_VERSION="$(php -r "echo preg_replace('/^([0-9]+\.[0-9]+)\.[0-9]+/', '\$1', phpversion());")" +fi diff --git a/tests/setup.cfg b/tests/setup.cfg new file mode 100644 index 0000000000..7da1f9608e --- /dev/null +++ b/tests/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 100 diff --git a/tests/skip_slow_tests.sh b/tests/skip_slow_tests.sh new file mode 100755 index 0000000000..9ba74745ee --- /dev/null +++ b/tests/skip_slow_tests.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +# Let tests know we should skip slow tests. +export PHP_CURL_CLASS_SKIP_SLOW_TESTS="1" + +source "run.sh" diff --git a/tests/test_all.sh b/tests/test_all.sh index 11b09b3512..118e747ecc 100755 --- a/tests/test_all.sh +++ b/tests/test_all.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" diff --git a/www/index.php b/www/index.php index 73289aef3d..84fe67b2e1 100644 --- a/www/index.php +++ b/www/index.php @@ -67,95 +67,165 @@ p:last-child { font-size: 200%; } + -.pl-c { - color: #969896; -} + + @@ -163,19 +233,28 @@

Easily send HTTP requests and integrate with web APIs

- - $curl = new Curl();
- $curl->get('https://www.example.com/');
-
- if ($curl->error) {
-     echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n";
- } else {
-     echo 'Success! Here is the response:' . "\n";
-     var_dump($curl->response);
- }
-
+
+$curl = new Curl();
+$curl->get('https://www.example.com/');
+
+if ($curl->error) {
+    echo 'Error: ' . $curl->errorMessage . "\n";
+    $curl->diagnose();
+} else {
+    echo 'Success! Here is the response:' . "\n";
+    var_dump($curl->response);
+}
+
+    
+

+ + https://github.com/php-curl-class/php-curl-class + +

+

+ src="https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/ci.yml?style=flat-square&label=build&branch=master" /> + + + get('https://www.example.com/'); + +if ($curl->error) { + echo 'Error: ' . $curl->errorMessage . "\n"; + $curl->diagnose(); +} else { + echo 'Success! Here is the response:' . "\n"; + var_dump($curl->response); +} diff --git a/www/scripts/highlight_code.js b/www/scripts/highlight_code.js new file mode 100644 index 0000000000..f0f7b76abb --- /dev/null +++ b/www/scripts/highlight_code.js @@ -0,0 +1,11 @@ +const fs = require('fs'); +const Prism = require('prismjs'); +const loadLanguages = require('prismjs/components/'); +loadLanguages(['php']); + +const source = fs.readFileSync('code.php', 'utf8'); +const lines = source.split(/\n/); +const code = lines.slice(1).join('\n'); +const html = Prism.highlight(code, Prism.languages.php, 'php'); + +console.log(html); diff --git a/www/scripts/highlight_code.sh b/www/scripts/highlight_code.sh new file mode 100755 index 0000000000..0aacac7b64 --- /dev/null +++ b/www/scripts/highlight_code.sh @@ -0,0 +1,2 @@ +npm install prismjs +node highlight_code.js diff --git a/www/scripts/package.json b/www/scripts/package.json new file mode 100644 index 0000000000..43c0c0963d --- /dev/null +++ b/www/scripts/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "prismjs": "^1.30.0" + } +}