Repository: CodeDredd/laravel-soap Branch: master Commit: bfdc0d99b6aa Files: 60 Total size: 143.1 KB Directory structure: gitextract_95jpnp3a/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── pr-labeler.yml │ ├── release-drafter.yml │ ├── stale.yml │ └── workflows/ │ ├── dependabot-auto-merge.yml │ ├── mkdocs.yml │ ├── php-cs-fixer.yml │ ├── phpstan.yml │ ├── pr-labeler.yml │ ├── releaseDrafter.yml │ └── tests.yml ├── .gitignore ├── .mailmap ├── .php_cs.dist.php ├── .styleci.yml ├── LICENSE ├── README.md ├── composer.json ├── config/ │ └── soap.php ├── mkdocs.yml ├── src/ │ ├── Client/ │ │ ├── Events/ │ │ │ ├── ConnectionFailed.php │ │ │ ├── RequestSending.php │ │ │ └── ResponseReceived.php │ │ ├── Request.php │ │ ├── Response.php │ │ └── ResponseSequence.php │ ├── Driver/ │ │ └── ExtSoap/ │ │ └── ExtSoapEngineFactory.php │ ├── Exceptions/ │ │ ├── NotFoundConfigurationException.php │ │ ├── RequestException.php │ │ └── SoapException.php │ ├── Facades/ │ │ └── Soap.php │ ├── Faker/ │ │ ├── EngineFaker.php │ │ └── fake.wsdl │ ├── Middleware/ │ │ ├── CisDhlMiddleware.php │ │ └── WsseMiddleware.php │ ├── Ray/ │ │ ├── LaravelRay.php │ │ └── SoapClientWatcher.php │ ├── SoapClient.php │ ├── SoapFactory.php │ ├── SoapServiceProvider.php │ ├── SoapTesting.php │ └── Xml/ │ └── XMLSerializer.php └── tests/ ├── Fixtures/ │ ├── CustomSoapClient.php │ ├── Responses/ │ │ └── SoapFault.xml │ └── Wsdl/ │ └── weather.wsdl ├── TestCase.php └── Unit/ ├── Client/ │ └── ResponseTest.php ├── Commands/ │ ├── MakeClientCommandTest.php │ └── MakeValidationCommandTest.php ├── Middleware/ │ └── CisDhlMiddlewareTest.php ├── SoapClientTest.php └── Xml/ └── XmlSerializerTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto /.github export-ignore /tests export-ignore .editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore .styleci.yml export-ignore CHANGELOG-* export-ignore CODE_OF_CONDUCT.md export-ignore CONTRIBUTING.md export-ignore phpunit.xml export-ignore ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: [codedredd] #open_collective: codedredd #custom: ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ### Reproduction If possible, provide a boiled down editable reproduction using a service like JSFiddle, Codepen, CodeSandbox, or a GitHub repository based on this template: https://github.com/piniajs/bug-report. A failing unit test is even better! Otherwise provide as much information as possible to reproduce the problem. You can find examples of different environments at https://github.com/piniajs?q=example&type=source and use them as a bug reproduction. If no reproduction is provided and the information is not enough to reproduce the problem, we won't be able to give it a look **and the issue will be converted into a question and moved to discussions**. ### Steps to reproduce the behavior 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Expected behavior A clear and concise description of what you expected to happen. ### Actual behavior A clear and concise description of what actually happens. ### Additional information Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Ask a question url: https://github.com/CodeDredd/laravel-soap/discussions/new?category=q-a about: Ask the community for help - name: Request a feature url: https://github.com/CodeDredd/laravel-soap/discussions/new?category=ideas about: Share ideas for new features - name: Report a security issue url: https://github.com/CodeDredd/laravel-soap/security/policy about: Learn how to notify us for sensitive bugs - name: Report a bug url: https://github.com/CodeDredd/laravel-soap/issues/new about: Report a reproducable bug ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- ### What problem is this solving A clear and concise description of what the problem is. Ex. when using the function X we cannot do Y. ### Proposed solution A clear and concise description of what you want to happen with an API proposal when applicable ### Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Issue: ## What I did ## How to test - Does this need an update to the documentation? If your answer is yes to any of these, please make sure to include it in your PR. ================================================ FILE: .github/dependabot.yml ================================================ # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "dependencies" ================================================ FILE: .github/pr-labeler.yml ================================================ feature: ['feature/*', 'feat/*'] fix: fix/* chore: chore/* ================================================ FILE: .github/release-drafter.yml ================================================ name-template: 'v$NEXT_PATCH_VERSION' tag-template: 'v$NEXT_PATCH_VERSION' categories: - title: '🚀 Features' labels: - 'feature' - 'enhancement' - title: '🐛 Bug Fixes' labels: - 'fix' - 'bugfix' - 'bug' - title: '🧰 Maintenance' labels: - 'chore' - 'documentation' - 'dependencies' - title: ':boom: BREAKING CHANGE' label: 'BREAKING CHANGE' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' template: | ## Changes $CHANGES ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 21 # Number of days of inactivity before a stale issue is closed daysUntilClose: 60 # Issues with these labels will never be considered stale exemptLabels: - todo - 'in progress' # Label to use when marking an issue as stale staleLabel: inactive # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks! # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Laravel Soap! ================================================ FILE: .github/workflows/dependabot-auto-merge.yml ================================================ name: dependabot-auto-merge on: pull_request_target permissions: pull-requests: write contents: write jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.6.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Auto-merge Dependabot PRs for semver-minor updates if: ${{steps.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: Auto-merge Dependabot PRs for semver-patch updates if: ${{steps.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}} ================================================ FILE: .github/workflows/mkdocs.yml ================================================ name: documentation on: push: branches: - master jobs: build: name: Deploy docs runs-on: ubuntu-latest steps: - name: Checkout master uses: actions/checkout@v3 - name: Deploy docs uses: mhausenblas/mkdocs-deploy-gh-pages@master env: PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }} ================================================ FILE: .github/workflows/php-cs-fixer.yml ================================================ name: Check & fix styling on: [push] jobs: php-cs-fixer: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} - name: Run PHP CS Fixer uses: docker://oskarstark/php-cs-fixer-ga with: args: --config=.php_cs.dist.php --allow-risky=yes # - name: Commit changes # uses: stefanzweifel/git-auto-commit-action@v4 # with: # commit_message: Fix styling ================================================ FILE: .github/workflows/phpstan.yml ================================================ name: PHPStan on: push: paths: - '**.php' - 'phpstan.neon' jobs: phpstan: name: phpstan runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.2' coverage: none - name: Install composer dependencies uses: ramsey/composer-install@v2 - name: Run PHPStan run: ./vendor/bin/phpstan --error-format=github ================================================ FILE: .github/workflows/pr-labeler.yml ================================================ name: PR Labeler on: pull_request: types: [opened] jobs: pr-labeler: runs-on: ubuntu-latest steps: - uses: TimonVS/pr-labeler-action@v4 with: configuration-path: .github/pr-labeler.yml # optional, .github/pr-labeler.yml is the default value env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/releaseDrafter.yml ================================================ name: Release Drafter on: push: # branches to consider in the event; optional, defaults to all branches: - master - '1.0' jobs: update_release_draft: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - uses: release-drafter/release-drafter@v5.25.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/tests.yml ================================================ name: Unit Tests on: push env: # see https://github.com/composer/composer/issues/9368#issuecomment-718112361 COMPOSER_ROOT_VERSION: "dev-master" jobs: provide_php_versions_json: runs-on: ubuntu-latest steps: # git clone + use PHP + composer install - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: 8.2 tools: composer:v2 - run: composer install --no-progress --ansi - # to see the output run: vendor/bin/easy-ci php-versions-json # here we create the json, we need the "id:" so we can use it in "outputs" bellow - id: output_data run: echo "::set-output name=matrix::$(vendor/bin/easy-ci php-versions-json)" # here, we save the result of this 1st phase to the "outputs" outputs: matrix: ${{ steps.output_data.outputs.matrix }} unit_tests: needs: provide_php_versions_json runs-on: ubuntu-latest strategy: fail-fast: false matrix: php: ${{ fromJson(needs.provide_php_versions_json.outputs.matrix) }} name: PHP ${{ matrix.php }} tests steps: - uses: actions/checkout@v3 # required for "git tag" presence for changelog-linker git tags resolver; default is 1 # https://github.com/actions/checkout#fetch-all-tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* # see https://github.com/shivammathur/setup-php - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: mbstring, intl ini-values: post_max_size=256M, max_execution_time=180 coverage: xdebug tools: php-cs-fixer, phpunit, composer:v2 # composer install cache - https://github.com/ramsey/composer-install - if: "matrix.php == 7.3" run: composer update --no-progress --ansi --prefer-lowest - if: "matrix.php == 7.4" uses: "ramsey/composer-install@v2" - if: "matrix.php >= 8" uses: "ramsey/composer-install@v2" with: composer-options: "--ignore-platform-req php" - run: vendor/bin/phpunit ================================================ FILE: .gitignore ================================================ .idea .env .php_cs .php_cs.cache .phpunit.result.cache build composer.lock coverage docs phpunit.xml phpstan.neon testbench.yaml vendor .php-cs-fixer.cache ray.php /docs/v3/node_modules/ /.phpunit.cache ================================================ FILE: .mailmap ================================================ CodeDredd Gregor Becker CodeDredd Gregor Becker ================================================ FILE: .php_cs.dist.php ================================================ in([ __DIR__ . '/src', __DIR__ . '/tests', ]) ->name('*.php') ->notName('*.blade.php') ->ignoreDotFiles(true) ->ignoreVCS(true); return (new PhpCsFixer\Config()) ->setRules([ '@PSR12' => true, 'array_syntax' => ['syntax' => 'short'], 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'no_unused_imports' => true, 'not_operator_with_successor_space' => true, 'trailing_comma_in_multiline' => true, 'phpdoc_scalar' => true, 'unary_operator_spaces' => true, 'binary_operator_spaces' => true, 'blank_line_before_statement' => [ 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], ], 'phpdoc_single_line_var_spacing' => true, 'phpdoc_var_without_name' => true, 'class_attributes_separation' => [ 'elements' => [ 'method' => 'one', ], ], 'method_argument_space' => [ 'on_multiline' => 'ensure_fully_multiline', 'keep_multiple_spaces_after_comma' => true, ], 'single_trait_insert_per_statement' => true, ]) ->setFinder($finder); ================================================ FILE: .styleci.yml ================================================ preset: laravel ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Michael van de Rijt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Laravel SOAP

# Laravel SOAP Client [![Software License](https://img.shields.io/github/license/codedredd/laravel-soap?style=flat-square)](LICENSE.md) [![Total Downloads](https://img.shields.io/packagist/dt/codedredd/laravel-soap?style=flat-square)](https://packagist.org/packages/codedredd/laravel-soap) [![test](https://img.shields.io/github/workflow/status/codedredd/laravel-soap/test?label=test&logo=github&style=flat-square)](https://github.com/CodeDredd/laravel-soap/actions?query=workflow%3Atest) [![styleci](https://github.styleci.io/repos/7548986/shield)](https://github.styleci.io/repos/257192373) [![version](https://img.shields.io/github/v/release/codedredd/laravel-soap?style=flat-square)](https://github.com/CodeDredd/laravel-soap/releases) [![documentation](https://img.shields.io/github/workflow/status/codedredd/laravel-soap/documentation?label=docs&logo=read-the-docs&style=flat-square)](https://codedredd.github.io/laravel-soap/) ## Versions | Laravel SOAP Version | Laravel Support | PHP Version | |----------------------|-----------------|-------------| | 1.x | 5.6, 6.x, 7.x | 7.3 - 8.0 | | 2.x | 8.x | 7.3 - 8.0 | | 3.x | 9.x | 8.0 - 8.1 | | 4.x | 9.x, 10.x | 8.1 - 8.2 | ## Installation Execute the following command to get the latest version of the package: composer require codedredd/laravel-soap ## Documentation You can find here a detailed [documentation](https://codedredd.github.io/laravel-soap/) ## Help me keep working on this project 💚 - [Become a Sponsor on GitHub](https://github.com/sponsors/codedredd) - [One-time donation via PayPal](https://paypal.me/dredd1984)

Platinum Sponsors

Gold Sponsors

Silver Sponsors

Bronze Sponsors

--- ## Contributing Please post issues and send PRs. ## License Laravel Soap is open-sourced software licensed under the MIT license. ================================================ FILE: composer.json ================================================ { "name": "codedredd/laravel-soap", "description": "A SoapClient wrapper integration for Laravel", "keywords": [ "laravel", "soap", "client", "wrapper" ], "license": "MIT", "authors": [ { "name": "Gregor Becker", "email": "gregor@codedredd.de" } ], "require": { "php": "~8.1 || ~8.2 || ~8.3 || ~8.4", "ext-soap": "*", "ext-bcmath": "*", "ext-intl": "*", "ext-json": "*", "ext-dom": "*", "ext-simplexml": "*", "illuminate/http": "^9.0 || ^10.0 || ^11.0 || ^12.0", "illuminate/support": "^9.0 || ^10.0 || ^11.0 || ^12.0", "phpro/soap-client": "^2.3.0 || ^3.1.0", "php-http/guzzle7-adapter": "^1.0", "php-http/discovery": "^1.14", "php-http/message": "^1.13", "php-http/client-common": "^2.6", "robrichards/wse-php": "^2.0", "php-soap/psr18-transport": "^1.7", "php-soap/psr18-wsse-middleware": "^2.6", "veewee/xml": "^2.6 || ^3.0", "spatie/laravel-package-tools": "^1.92", "illuminate/contracts": "^9.0 || ^10.0 || ^11.0 || ^12.0" }, "require-dev": { "symfony/options-resolver": "^6.2.5", "phpunit/phpunit": "^10.0 || ^11.0", "orchestra/testbench": "^8.0 || ^9.0 || ^10.0", "laminas/laminas-code": "^4.8.0", "nunomaduro/collision": "^8.1", "symplify/easy-ci": "^10.0", "spatie/laravel-ray": "^1.32", "larastan/larastan": "^3.1" }, "autoload": { "psr-4": { "CodeDredd\\Soap\\": "src/" } }, "autoload-dev": { "psr-4": { "CodeDredd\\Soap\\Tests\\": "tests/" } }, "extra": { "laravel": { "providers": [ "CodeDredd\\Soap\\SoapServiceProvider" ], "aliases": { "SOAP": "SoapFacade" } } }, "minimum-stability": "dev", "prefer-stable": true, "config": { "allow-plugins": { "php-http/discovery": true } } } ================================================ FILE: config/soap.php ================================================ [ 'path' => app_path('Soap'), 'namespace' => 'App\\Soap', ], /* |-------------------------------------------------------------------------- | SOAP Ray Configuration |-------------------------------------------------------------------------- | | Define if all requests should go to ray */ 'ray' => [ 'send_soap_client_requests' => false, ], /* |-------------------------------------------------------------------------- | SOAP Call behaviour |-------------------------------------------------------------------------- | | Define if the arguments should be wrapped in an array */ 'call' => [ 'wrap_arguments_in_array' => true, ], /* |-------------------------------------------------------------------------- | SOAP Client Configuration |-------------------------------------------------------------------------- | | Her you can setup your soap client by configuration so that ou just need | a name. | | example: Soap::buildClient('laravel_soap') */ 'clients' => [ 'laravel_soap' => [ 'base_wsdl' => 'test.wsdl', 'with_wsa' => true, 'with_basic_auth' => [ 'username' => 'username', 'password' => 'password', ], 'with_wsse' => [ 'user_token_name' => 'username', 'user_token_password' => 'password', 'private_key_file' => 'path/to/privatekey.pem', 'public_key_file' => 'path/to/publickey.pyb', 'server_certificate_file' => 'path/to/client-cert.pem', 'server_certificate_has_subject_key_identifier' => false, 'user_token_digest' => false, 'digital_sign_method' => XMLSecurityKey::RSA_SHA1, 'timestamp' => 3600, 'sign_all_headers' => false, ], ], ], ]; ================================================ FILE: mkdocs.yml ================================================ site_name: Laravel SOAP Docs site_description: 'Laravel SOAP Documentation' site_author: 'Gregor Becker' docs_dir: docs/v2/ repo_name: 'codedredd/laravel-soap' repo_url: 'https://github.com/codedredd/laravel-soap' # Copyright copyright: Copyright © 2020 - 2020 Gregor Becker nav: - 'Get started': 'index.md' - Client: - Request: 'client/request.md' - Response: 'client/response.md' - authentication: 'client/authentication.md' - configuration: 'client/configuration.md' - Commands: 'commands.md' - Testing: 'testing.md' theme: name: 'material' language: 'en' palette: primary: 'cyan' accent: 'deep orange' markdown_extensions: - admonition - attr_list - codehilite: guess_lang: false - toc: permalink: true - pymdownx.betterem: smart_enable: all - pymdownx.superfences - pymdownx.inlinehilite - pymdownx.tabbed - pymdownx.emoji: emoji_index: !!python/name:materialx.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.highlight: extend_pygments_lang: - name: php-inline lang: php options: startinline: true ================================================ FILE: src/Client/Events/ConnectionFailed.php ================================================ request = $request; } } ================================================ FILE: src/Client/Events/RequestSending.php ================================================ request = $request; } } ================================================ FILE: src/Client/Events/ResponseReceived.php ================================================ request = $request; $this->response = $response; } } ================================================ FILE: src/Client/Request.php ================================================ request = $request; } /** * Get the soap action for soap 1.1 and 1.2. */ public function action(): string { return Str::remove('"', SoapActionDetector::detectFromRequest($this->request)); } public function getRequest(): RequestInterface { return $this->request; } /** * Get the URL of the request. */ public function url(): string { return (string) $this->request->getUri(); } /** * Determine if the request has a given header. * * @param string $key * @param mixed $value * @return bool */ public function hasHeader($key, $value = null) { if (is_null($value)) { return ! empty($this->request->getHeaders()[$key]); } $headers = $this->headers(); if (! Arr::has($headers, $key)) { return false; } $value = is_array($value) ? $value : [$value]; return empty(array_diff($value, $headers[$key])); } /** * Determine if the request has the given headers. * * @param array|string $headers * @return bool */ public function hasHeaders($headers) { if (is_string($headers)) { $headers = [$headers => null]; } foreach ($headers as $key => $value) { if (! $this->hasHeader($key, $value)) { return false; } } return true; } /** * Get the values for the header with the given name. * * @param string $key * @return array */ public function header($key) { return Arr::get($this->headers(), $key, []); } /** * Get the request headers. * * @return array */ public function headers() { return $this->request->getHeaders(); } /** * Get the body of the request. * * @return string */ public function body() { return (string) $this->request->getBody(); } /** * Return complete xml request body. */ public function xmlContent(): string { return $this->request->getBody()->getContents(); } /** * Return request arguments. * * @throws EncodingException */ public function arguments(): array { $doc = Document::fromXmlString($this->body()); $wrappedArguments = config()->get('soap.call.wrap_arguments_in_array', true); $method = $doc->locate(new SoapBodyLocator()); if ($wrappedArguments) { $method = $method?->firstElementChild; } return Arr::wrap(Arr::get(element_decode($method, traverse(new RemoveNamespaces())), $wrappedArguments ? 'node' : 'Body', [])); } /** * Set the decoded data on the request. * * @param array $data * @return $this */ public function withData(array $data) { $this->data = $data; return $this; } /** * Get the underlying PSR compliant request instance. * * @return \Psr\Http\Message\RequestInterface */ public function toPsrRequest() { return $this->request; } /** * Determine if the given offset exists. * * @param string $offset * @return bool * * @throws EncodingException */ public function offsetExists(string $offset): bool { return isset($this->arguments()[$offset]); } /** * Get the value for a given offset. * * @param string $offset * @return mixed * * @throws EncodingException */ public function offsetGet($offset): mixed { return $this->arguments()[$offset]; } /** * Set the value at the given offset. * * @param string $offset * @param mixed $value * @return void * * @throws LogicException */ public function offsetSet($offset, $value): void { throw new LogicException('Request data may not be mutated using array access.'); } /** * Unset the value at the given offset. * * @param string $offset * @return void * * @throws LogicException */ public function offsetUnset($offset): void { throw new LogicException('Request data may not be mutated using array access.'); } } ================================================ FILE: src/Client/Response.php ================================================ response = $response; } public function setCookies(array $cookies): void { $this->cookies = $cookies; } public function setTransferStats(?TransferStats $transferStats): void { $this->transferStats = $transferStats; } public static function fromSoapResponse(mixed $result, int $status = 200): Response { return new self(new Psr7Response($status, [], json_encode($result))); } public static function fromSoapFault(\SoapFault $soapFault): Response { return new self(new Psr7Response(400, [], $soapFault->getMessage())); } /** * Get the underlying PSR response for the response. */ public function toPsrResponse(): ResponseInterface { return $this->response; } /** * Get the JSON decoded body of the response as an object. * * @return object */ public function object(): object { return json_decode($this->body(), false); } /** * Get the JSON decoded body of the response as a collection. * * @param string|null $key * @return \Illuminate\Support\Collection */ public function collect(string $key = null): Collection { return Collection::make($this->json($key)); } /** * Get the body of the response. * * @param bool $transformXml * @param bool $sanitizeXmlFaultMessage * @return string */ public function body(bool $transformXml = true, bool $sanitizeXmlFaultMessage = true): string { $body = (string) $this->response->getBody(); if ($transformXml && Str::contains($body, 'xpath() ->evaluate('string(.//faultstring)', string()) ?? 'No Fault Message found'; return trim($sanitizeXmlFaultMessage ? Str::after($message, 'Exception:') : $message); } return $body; } /** * Determine if the request was successful. * * @return bool */ public function successful(): bool { return $this->status() >= 200 && $this->status() < 300; } /** * Get the status code of the response. * * @return int */ public function status(): int { return (int) $this->response->getStatusCode(); } /** * Determine if the response code was "OK". */ public function ok(): bool { return $this->status() === 200; } /** * Determine if the response indicates a client or server error occurred. */ public function failed(): bool { return $this->serverError() || $this->clientError(); } /** * Determine if the response was a redirect. */ public function redirect(): bool { return $this->status() >= 300 && $this->status() < 400; } /** * Throw an exception if a server or client error occurred. * * @return $this * * @throws \CodeDredd\Soap\Exceptions\RequestException */ public function throw() { $callback = func_get_args()[0] ?? null; if ($this->failed()) { throw tap(new RequestException($this), function ($exception) use ($callback) { if ($callback && is_callable($callback)) { $callback($this, $exception); } }); } return $this; } /** * Throw an exception if a server or client error occurred and the given condition evaluates to true. * * @param bool $condition * @return $this * * @throws \CodeDredd\Soap\Exceptions\RequestException */ public function throwIf(bool $condition): Response|static { return $condition ? $this->throw() : $this; } /** * Determine if the response indicates a server error occurred. */ public function serverError(): bool { return $this->status() >= 500; } /** * Determine if the response indicates a client error occurred. */ public function clientError(): bool { return $this->status() >= 400 && $this->status() < 500; } /** * Execute the given callback if there was a server or client error. * * @param \Closure|callable $callback * @return \CodeDredd\Soap\Client\Response */ public function onError(callable $callback) { if ($this->failed()) { $callback($this); } return $this; } /** * Get the handler stats of the response. * * @return array */ public function handlerStats() { return $this->transferStats?->getHandlerStats() ?? []; } /** * Get the JSON decoded body of the response as an array. */ public function json($key = null, $default = null): ?array { if (! $this->decoded) { $this->decoded = json_decode($this->body(), true); } if (is_null($key)) { return $this->decoded; } return data_get($this->decoded, $key, $default); } /** * Get a header from the response. */ public function header(string $header): string { return $this->response->getHeaderLine($header); } /** * Get the headers from the response. */ public function headers(): array { return $this->response->getHeaders(); } /** * Determine if the given offset exists. * * @param string $offset * @return bool */ public function offsetExists($offset): bool { return isset($this->json()[$offset]); } /** * Get the value for a given offset. * * @param string $offset * @return mixed */ public function offsetGet($offset): mixed { return $this->json()[$offset]; } /** * Set the value at the given offset. * * @param string $offset * @param mixed $value * @return void * * @throws \LogicException */ public function offsetSet($offset, $value): void { throw new LogicException('Response data may not be mutated using array access.'); } /** * Unset the value at the given offset. * * @param string $offset * @return void * * @throws \LogicException */ public function offsetUnset($offset): void { throw new LogicException('Response data may not be mutated using array access.'); } /** * Get the body of the response. * * @return string */ public function __toString() { return $this->body(); } /** * Dynamically proxy other methods to the underlying response. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { return static::hasMacro($method) ? $this->macroCall($method, $parameters) : $this->response->{$method}(...$parameters); } } ================================================ FILE: src/Client/ResponseSequence.php ================================================ responses = $responses; } /** * Push a response to the sequence. * * @param string|array $body * @param int $status * @param array $headers * @return $this */ public function push($body = '', int $status = 200, array $headers = []) { return $this->pushResponse( SoapFactory::response($body, $status, $headers) ); } /** * Push a response to the sequence. * * @param mixed $response * @return $this */ public function pushResponse($response) { $this->responses[] = $response; return $this; } /** * Push a response with the given status code to the sequence. * * @param int $status * @param array $headers * @return $this */ public function pushStatus(int $status, array $headers = []) { return $this->pushResponse( SoapFactory::response('', $status, $headers) ); } /** * Push response with the contents of a file as the body to the sequence. * * @param string $filePath * @param int $status * @param array $headers * @return $this */ public function pushFile(string $filePath, int $status = 200, array $headers = []) { $string = file_get_contents($filePath); return $this->pushResponse( SoapFactory::response($string, $status, $headers) ); } /** * Make the sequence return a default response when it is empty. * * @return $this */ public function dontFailWhenEmpty() { return $this->whenEmpty(SoapFactory::response()); } /** * Make the sequence return a default response when it is empty. * * @param \GuzzleHttp\Promise\PromiseInterface|\Closure $response * @return $this */ public function whenEmpty($response) { $this->failWhenEmpty = false; $this->emptyResponse = $response; return $this; } /** * Indicate that this sequence has depleted all of its responses. * * @return bool */ public function isEmpty() { return count($this->responses) === 0; } /** * Get the next response in the sequence. * * @return mixed */ public function __invoke() { if ($this->failWhenEmpty && count($this->responses) === 0) { throw new OutOfBoundsException('A request was made, but the response sequence is empty.'); } if (! $this->failWhenEmpty && count($this->responses) === 0) { return value($this->emptyResponse ?? SoapFactory::response()); } return array_shift($this->responses); } } ================================================ FILE: src/Driver/ExtSoap/ExtSoapEngineFactory.php ================================================ getMessage(), (int) $throwable->getCode(), $throwable); } } ================================================ FILE: src/Exceptions/RequestException.php ================================================ status()}:\n {$response->body()}", $response->status() ); $this->response = $response; } } ================================================ FILE: src/Exceptions/SoapException.php ================================================ getMessage(), (int) $throwable->getCode(), $throwable); } } ================================================ FILE: src/Facades/Soap.php ================================================ driver = $driver; $this->transport = $transport; $this->options = $options; } public function request(string $method, array $arguments) { $request = new SoapRequest(XMLSerializer::arrayToSoapXml($arguments), $this->options->getWsdl(), $method, $this->options->getOptions()['soap_version'] ?? SOAP_1_1); $response = $this->transport->request($request); return json_decode($response->getPayload()); } public function getMetadata(): Metadata { return $this->driver->getMetadata(); } } ================================================ FILE: src/Faker/fake.wsdl ================================================ Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only ================================================ FILE: src/Middleware/CisDhlMiddleware.php ================================================ user = $user; $this->signature = $signature; } public function getName(): string { return 'cis_dhl_middleware'; } public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { return $next( (new XmlMessageManipulator())( $request, function (Document $document) { $builder = new SoapHeaders( new SoapHeader( self::CIS_NS, 'cis:Authentification', children( namespaced_element(self::CIS_NS, 'user', value($this->user)), namespaced_element(self::CIS_NS, 'signature', value($this->signature)) ) ) ); $headers = $document->build($builder); return $document->manipulate(new PrependSoapHeaders(...$headers)); } ) ); } /** * @param ResponseInterface $response * @return ResponseInterface */ public function afterResponse(ResponseInterface $response): ResponseInterface { return $response; } } ================================================ FILE: src/Middleware/WsseMiddleware.php ================================================ $value) { $this->{$key} = $value; } } public function getName(): string { return 'wsse_middleware'; } public function withTimestamp(int $timestamp = 3600): self { $this->timestamp = $timestamp; return $this; } public function withAllHeadersSigned(): self { $this->signAllHeaders = true; return $this; } public function withDigitalSignMethod(string $digitalSignMethod): self { $this->digitalSignMethod = $digitalSignMethod; return $this; } public function withUserToken(string $username, string $password = null, $digest = false): self { $this->userTokenName = $username; $this->userTokenPassword = $password; $this->userTokenDigest = $digest; return $this; } public function withEncryption(string $serverCertificateFile): self { $this->encrypt = true; $this->serverCertificateFile = $serverCertificateFile; return $this; } public function withServerCertificateHasSubjectKeyIdentifier(bool $hasSubjectKeyIdentifier): self { $this->serverCertificateHasSubjectKeyIdentifier = $hasSubjectKeyIdentifier; return $this; } public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { return $this->beforeRequest($next, $request)->then( fn (ResponseInterface $response): ResponseInterface => $this->afterResponse($response) ); } public function beforeRequest(callable $handler, RequestInterface $request): Promise { $request = (new XmlMessageManipulator())( $request, function (Document $xml) { $wsse = new WSSESoap($xml->toUnsafeDocument(), $this->mustUnderstand); // Prepare the WSSE soap class: $wsse->signAllHeaders = $this->signAllHeaders; $wsse->addTimestamp($this->timestamp); // Add a user token if this is configured. if ($this->hasUserToken) { $wsse->addUserToken($this->userTokenName, $this->userTokenPassword, $this->userTokenDigest); } if (! empty($this->privateKeyFile) && ! empty($this->publicKeyFile)) { // Add certificate (BinarySecurityToken) to the message $token = $wsse->addBinaryToken(file_get_contents($this->publicKeyFile)); // Create new XMLSec Key using the dsigType and type is private key $key = new XMLSecurityKey($this->digitalSignMethod, ['type' => 'private']); $key->loadKey($this->privateKeyFile, true); $wsse->signSoapDoc($key); // Attach token pointer to Signature: $wsse->attachTokentoSig($token); } // Add end-to-end encryption if configured: if ($this->encrypt) { $key = new XMLSecurityKey(XMLSecurityKey::AES256_CBC); $key->generateSessionKey(); $siteKey = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, ['type' => 'public']); $siteKey->loadKey($this->serverCertificateFile, true, true); $wsse->encryptSoapDoc($siteKey, $key, [ 'KeyInfo' => [ 'X509SubjectKeyIdentifier' => $this->serverCertificateHasSubjectKeyIdentifier, ], ]); } } ); return $handler($request); } public function afterResponse(ResponseInterface $response): ResponseInterface { if (! $this->encrypt) { return $response; } return (new XmlMessageManipulator())( $response, function (Document $xml) { $wsse = new WSSESoap($xml->toUnsafeDocument()); $wsse->decryptSoapDoc( $xml->toUnsafeDocument(), [ 'keys' => [ 'private' => [ 'key' => $this->privateKeyFile, 'isFile' => true, 'isCert' => false, ], ], ] ); } ); } } ================================================ FILE: src/Ray/LaravelRay.php ================================================ handleWatcherCallable($watcher, $callable); }); SpatieRay::macro('stopShowingSoapClientRequests', fn () => app(SoapClientWatcher::class)->disable()); } protected function handleWatcherCallable(Watcher $watcher, Closure $callable = null): RayProxy { $rayProxy = new RayProxy(); $wasEnabled = $watcher->enabled(); $watcher->enable(); $watcher->setRayProxy($rayProxy); if ($callable) { $callable(); if (! $wasEnabled) { $watcher->disable(); } } return $rayProxy; } } ================================================ FILE: src/Ray/SoapClientWatcher.php ================================================ enabled = config('soap.ray.send_soap_client_requests', false); Event::listen(RequestSending::class, function (RequestSending $event) { if (! $this->enabled()) { return; } $ray = $this->handleRequest($event->request); optional($this->rayProxy)->applyCalledMethods($ray); }); Event::listen(ResponseReceived::class, function (ResponseReceived $event) { if (! $this->enabled()) { return; } $ray = $this->handleResponse($event->request, $event->response); optional($this->rayProxy)->applyCalledMethods($ray); }); } protected function handleRequest(Request $request) { $payload = new TablePayload([ 'Action' => $request->action(), 'URL' => $request->url(), 'Headers' => $request->headers(), 'Data' => $request->arguments(), 'Body' => $request->body(), ], 'SOAP'); return app(Ray::class)->sendRequest($payload); } protected function handleResponse(Request $request, Response $response) { $payload = new TablePayload([ 'URL' => $request->url(), 'Real Request' => ! empty($response->handlerStats()), 'Success' => $response->successful(), 'Status' => $response->status(), 'Headers' => $response->headers(), 'Body' => rescue(function () use ($response) { return $response->json(); }, $response->body(), false), 'Size' => $response->handlerStats()['size_download'] ?? null, 'Connection time' => $response->handlerStats()['connect_time'] ?? null, 'Duration' => $response->handlerStats()['total_time'] ?? null, 'Request Size' => $response->handlerStats()['request_size'] ?? null, ], 'SOAP'); return app(Ray::class)->sendRequest($payload); } } ================================================ FILE: src/SoapClient.php ================================================ factory = $factory; $this->client = new Client($this->guzzleClientOptions); $this->pluginClient = new PluginClient($this->client, $this->middlewares); $this->wsdlProvider = new FlatteningLoader(Psr18Loader::createForClient($this->pluginClient)); $this->beforeSendingCallbacks = collect([function (Request $request, array $options, SoapClient $soapClient) { $soapClient->request = $request; $soapClient->cookies = Arr::wrap($options['cookies']); $soapClient->dispatchRequestSendingEvent(); }]); } public function refreshWsdlProvider() { $this->wsdlProvider = new FlatteningLoader(Psr18Loader::createForClient($this->pluginClient)); return $this; } public function refreshPluginClient(): static { $this->pluginClient = new PluginClient($this->client, $this->middlewares); return $this; } public function getPluginClient(): PluginClient { return $this->pluginClient; } protected function setTransport(Transport $handler = null): static { $soapClient = AbusedClient::createFromOptions( ExtSoapOptions::defaults($this->wsdl, $this->options) ); $transport = $handler ?? Psr18Transport::createForClient($this->pluginClient); $this->transport = $handler ?? new TraceableTransport( $soapClient, $transport ); return $this; } /** * Add the given headers to the request. */ public function withHeaders(array $headers): static { return $this->withGuzzleClientOptions(array_merge_recursive($this->options, [ 'headers' => $headers, ])); } public function getTransport(): TraceableTransport|Transport { return $this->transport; } public function getClient(): Client|ClientInterface { return $this->client; } public function withGuzzleClientOptions(array ...$options): static { $this->guzzleClientOptions = array_merge_recursive($this->guzzleClientOptions, ...$options); $this->client = new Client($this->guzzleClientOptions); return $this; } public function getEngine(): Engine { return $this->engine; } /** * @return $this */ public function withRemoveEmptyNodes() { $this->middlewares = array_merge_recursive($this->middlewares, [ new RemoveEmptyNodesMiddleware(), ]); return $this; } /** * @param string|array $username * @param string|null $password * @return $this */ public function withBasicAuth($username, ?string $password = null) { if (is_array($username)) { ['username' => $username, 'password' => $password] = $username; } $this->withHeaders([ 'Authorization' => sprintf('Basic %s', base64_encode( sprintf('%s:%s', $username, $password) )), ]); return $this; } /** * @param string|array $user * @param string|null $signature * @return $this */ public function withCisDHLAuth($user, ?string $signature = null) { if (is_array($user)) { ['username' => $user, 'password' => $signature] = $user; } $this->middlewares = array_merge_recursive($this->middlewares, [ new CisDhlMiddleware($user, $signature), ]); return $this; } /** * @return $this */ public function withWsa() { $this->middlewares = array_merge_recursive($this->middlewares, [ new WsaMiddleware(), ]); return $this; } /** * @return $this */ public function withWsa2005() { $this->middlewares = array_merge_recursive($this->middlewares, [ new WsaMiddleware2005(), ]); return $this; } public function withWsse(array $options): static { $this->middlewares = array_merge_recursive($this->middlewares, [ new WsseMiddleware($options), ]); return $this; } /** * Merge new options into the client. * * @param array $options * @return $this */ public function withOptions(array $options) { return tap($this, function ($request) use ($options) { return $this->options = array_merge_recursive($this->options, $options); }); } /** * Merge the given options with the current request options. * * @param array $options * @return array */ public function mergeOptions(...$options) { return array_merge_recursive($this->options, ...$options); } /** * Make it possible to debug the last request. */ public function debugLastSoapRequest(): array { if ($this->transport instanceof TraceableTransport) { $lastRequestInfo = $this->transport->collectLastRequestInfo(); return [ 'request' => [ 'headers' => trim($lastRequestInfo->getLastRequestHeaders()), 'body' => $lastRequestInfo->getLastRequest(), ], 'response' => [ 'headers' => trim($lastRequestInfo->getLastResponseHeaders()), 'body' => $lastRequestInfo->getLastResponse(), ], ]; } return []; } /** * Handle a failed validation attempt. * * @param \Illuminate\Contracts\Validation\Validator $validator * @return Response */ protected function failedValidation(Validator $validator) { return Response::fromSoapResponse([ 'success' => false, 'message' => __('Invalid data.'), 'errors' => $validator->errors(), ]); } public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } return $this->call($method, $parameters[0] ?? $parameters); } public function call(string $method, Validator|array $arguments = []): Response { try { if (! $this->isClientBuilded) { $this->buildClient(); } $this->refreshEngine(); if ($arguments instanceof Validator) { if ($arguments->fails()) { return $this->failedValidation($arguments); } $arguments = $arguments->validated(); } $arguments = config()->get('soap.call.wrap_arguments_in_array', true) ? [$arguments] : $arguments; $result = $this->engine->request($method, $arguments); if ($result instanceof ResultProviderInterface) { return $this->buildResponse(Response::fromSoapResponse($result->getResult())); } if (! $result instanceof ResultInterface) { return $this->buildResponse(Response::fromSoapResponse($result)); } return $this->buildResponse(new Response(new Psr7Response(200, [], $result))); } catch (\Exception $exception) { if ($exception instanceof \SoapFault) { return $this->buildResponse(Response::fromSoapFault($exception)); } $previous = $exception->getPrevious(); $this->dispatchConnectionFailedEvent(); if ($previous instanceof HttpException) { /** @var HttpException $previous */ return new Response($previous->getResponse()); } throw SoapException::fromThrowable($exception); } } protected function buildResponse($response) { return tap($response, function ($result) { $this->populateResponse($result); $this->dispatchResponseReceivedEvent($result); }); } /** * Build the Soap client. * * @param string $setup * @return SoapClient * * @throws NotFoundConfigurationException */ public function buildClient(string $setup = '') { $this->byConfig($setup); $this->withGuzzleClientOptions([ 'handler' => $this->buildHandlerStack(), 'on_stats' => function ($transferStats) { $this->transferStats = $transferStats; }, ]); $this->isClientBuilded = true; return $this; } /** * @param string $setup * @return $this * * @throws NotFoundConfigurationException */ public function byConfig(string $setup) { if (! empty($setup)) { $setup = config()->get('soap.clients.'.$setup); if (! $setup) { throw new NotFoundConfigurationException($setup); } foreach ($setup as $setupItem => $setupItemConfig) { if (is_bool($setupItemConfig)) { $this->{Str::camel($setupItem)}(); } elseif (is_array($setupItemConfig)) { $this->{Str::camel($setupItem)}($this->arrayKeysToCamel($setupItemConfig)); } elseif (is_string($setupItemConfig)) { $this->{Str::camel($setupItem)}($setupItemConfig); } } } return $this; } /** * @param array $items * @return array */ protected function arrayKeysToCamel(array $items) { $changedItems = []; foreach ($items as $key => $value) { $changedItems[Str::camel($key)] = $value; } return $changedItems; } /** * Build the before sending handler stack. * * @return \GuzzleHttp\HandlerStack */ public function buildHandlerStack() { return tap(HandlerStack::create(), function ($stack) { $stack->push($this->buildBeforeSendingHandler(), 'before_sending'); $stack->push($this->buildRecorderHandler(), 'recorder'); $stack->push($this->buildStubHandler(), 'stub'); }); } /** * Build the before sending handler. */ public function buildBeforeSendingHandler(): Closure { return function ($handler) { return function ($request, $options) use ($handler) { return $handler($this->runBeforeSendingCallbacks($request, $options), $options); }; }; } /** * Execute the "before sending" callbacks. */ public function runBeforeSendingCallbacks(RequestInterface $request, array $options): mixed { return tap($request, function ($request) use ($options) { $this->beforeSendingCallbacks->each->__invoke( new Request($request), $options, $this ); }); } /** * Populate the given response with additional data. * * @param \CodeDredd\Soap\Client\Response $response * @return \CodeDredd\Soap\Client\Response */ protected function populateResponse(Response $response) { $response->setCookies($this->cookies); $response->setTransferStats($this->transferStats); return $response; } /** * Dispatch the RequestSending event if a dispatcher is available. * * @return void */ protected function dispatchRequestSendingEvent() { event(new RequestSending($this->request)); } /** * Dispatch the ResponseReceived event if a dispatcher is available. * * @param \CodeDredd\Soap\Client\Response $response * @return void */ protected function dispatchResponseReceivedEvent(Response $response) { if (! $this->request) { return; } event(new ResponseReceived($this->request, $response)); } /** * Dispatch the ConnectionFailed event if a dispatcher is available. * * @return void */ protected function dispatchConnectionFailedEvent() { event(new ConnectionFailed($this->request)); } /** * Build the recorder handler. * * @return Closure */ public function buildRecorderHandler() { return function ($handler) { return function ($request, $options) use ($handler) { $promise = $handler($request, $options); return $promise->then(function ($response) use ($request) { optional($this->factory)->recordRequestResponsePair( new Request($request), new Response($response) ); return $response; }); }; }; } /** * Build the stub handler. * * @return Closure */ public function buildStubHandler() { return function (callable $handler) { return function ($request, $options) use ($handler) { $response = ($this->stubCallbacks ?? collect()) ->map ->__invoke(new Request($request), $options) ->filter() ->first(); if (is_null($response)) { return $handler($request, $options); } elseif (is_array($response)) { return SoapFactory::response($response); } return $response; }; }; } /** * @return $this */ protected function refreshEngine() { $this->refreshPluginClient(); $this->setTransport(); $this->refreshExtSoapOptions(); $this->engine = ExtSoapEngineFactory::fromOptionsWithHandler( $this->extSoapOptions, $this->transport, $this->factory->isRecording() ); $this->refreshWsdlProvider(); return $this; } protected function refreshExtSoapOptions() { $this->extSoapOptions = ExtSoapOptions::defaults($this->wsdl, $this->options); if ($this->factory->isRecording()) { $this->wsdlProvider = new FlatteningLoader(new StreamWrapperLoader()); // $this->extSoapOptions->withWsdlProvider($this->wsdlProvider); } } /** * @param string $wsdl * @return $this */ public function baseWsdl(string $wsdl) { $this->wsdl = $wsdl; return $this; } /** * Register a stub callable that will intercept requests and be able to return stub responses. * * @param callable $callback * @return $this */ public function stub($callback) { $this->stubCallbacks = collect($callback); return $this; } } ================================================ FILE: src/SoapFactory.php ================================================ stubCallbacks = collect(); // $this->fakeWsdlLocation = __DIR__.'/Faker/fake.wsdl'; } /** * Execute a method against a new pending request instance. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } if (Str::contains($method, 'assert')) { return (new SoapTesting($this))->{$method}(...$parameters); } return tap($this->client(), function ($request) { $request->stub($this->stubCallbacks); })->{$method}(...$parameters); } public function isRecording() { return $this->recording; } public function getResponseSequences() { return $this->responseSequences; } public function getRecorded() { return $this->recorded; } /** * Record a request response pair. * * @param \CodeDredd\Soap\Client\Request $request * @param \CodeDredd\Soap\Client\Response $response * @return void */ public function recordRequestResponsePair($request, $response) { if ($this->recording) { $this->recorded[] = [$request, $response]; } } public function fakeWsdl(string $wsdl) { $this->fakeWsdlLocation = $wsdl; return $this; } public function getFakeWsdl() { return $this->fakeWsdlLocation; } /** * Register a response sequence for the given URL pattern. * * @param string $url * @return \CodeDredd\Soap\Client\ResponseSequence */ public function fakeSequence($url = '*') { return tap($this->sequence(), function ($sequence) use ($url) { $this->fake([$url => $sequence]); }); } /** * Get an invokable object that returns a sequence of responses in order for use during stubbing. * * @param array $responses * @return \CodeDredd\Soap\Client\ResponseSequence */ public function sequence(array $responses = []) { return $this->responseSequences[] = new ResponseSequence($responses); } /** * Register a stub callable that will intercept requests and be able to return stub responses. * * @param callable|array $callback * @return $this */ public function fake($callback = null) { $this->record(); if (is_null($callback)) { $callback = function () { return static::response(); }; } if (is_array($callback)) { $callback['*'] = $callback['*'] ?? self::response(); foreach ($callback as $method => $callable) { $this->stubMethod($method, $callable); } return $this; } $this->stubCallbacks = $this->stubCallbacks->merge(collect([ $callback instanceof Closure ? $callback : function () use ($callback) { return $callback; }, ])); return $this; } /** * Begin recording request / response pairs. * * @return $this */ protected function record() { $this->recording = true; return $this; } /** * Create a new response instance for use during stubbing. * * @param array|string|null $body * @param int $status * @param array $headers * @return \GuzzleHttp\Promise\PromiseInterface */ public static function response($body = null, $status = 200, $headers = []) { if (is_array($body)) { $body = json_encode($body); } elseif (is_string($body)) { $body = json_encode([ 'response' => $body, ]); } return Create::promiseFor(new Psr7Response($status, $headers, $body)); } /** * Stub the given URL using the given callback. * * @param string $method * @param \CodeDredd\Soap\Client\Response|\GuzzleHttp\Promise\PromiseInterface|callable $callback * @return $this */ public function stubMethod($method, $callback) { return $this->fake(function ($request, $options) use ($method, $callback) { if (! Str::is(Str::start($method, '*'), $request->action())) { return; } return $callback instanceof Closure || $callback instanceof ResponseSequence ? $callback($request, $options) : $callback; }); } /** * Get a collection of the request / response pairs matching the given truth test. * * @param callable $callback * @return \Illuminate\Support\Collection */ public function recorded($callback) { if (empty($this->recorded)) { return collect(); } $callback = $callback ?: function () { return true; }; return collect($this->recorded)->filter(function ($pair) use ($callback) { return $callback($pair[0], $pair[1]); }); } /** * Get a new client class instance. * * @return SoapClient */ public function client() { return new static::$clientClass($this); } /** * Set the client class name. * * @param string $clientClass */ public static function useClientClass(string $clientClass): void { static::$clientClass = $clientClass; } } ================================================ FILE: src/SoapServiceProvider.php ================================================ name('soap') ->hasConfigFile('soap'); } public function packageRegistered() { $this->registerService(); $this->registerRay(); } public function packageBooted() { $this->bootRay(); } /** * Register Soap's services in the container. * * @return void */ protected function registerService() { $this->app->bind('Soap', function () { return new SoapFactory(); }); } protected function registerRay() { if (! class_exists('Spatie\\LaravelRay\\Ray')) { return; } /** @var LaravelRay $macros */ $macros = app(LaravelRay::class); $macros->register(); $this->app->singleton(SoapClientWatcher::class); } protected function bootRay() { if (! class_exists('Spatie\\LaravelRay\\Ray')) { return; } /** @var Watcher $watcher */ $watcher = app(SoapClientWatcher::class); $watcher->register(); } } ================================================ FILE: src/SoapTesting.php ================================================ factory = $factory; } /** * Assert that a request / response pair was not recorded matching a given truth test. * * @param callable $callback * @return void */ public function assertNotSent($callback) { PHPUnit::assertFalse( $this->factory->recorded($callback)->count() > 0, 'Unexpected request was recorded.' ); } /** * Assert that no request / response pair was recorded. * * @return void */ public function assertNothingSent() { PHPUnit::assertEmpty( $this->factory->getRecorded(), 'Requests were recorded.' ); } /** * Assert that every created response sequence is empty. * * @return void */ public function assertSequencesAreEmpty() { foreach ($this->factory->getResponseSequences() as $responseSequence) { PHPUnit::assertTrue( $responseSequence->isEmpty(), 'Not all response sequences are empty.' ); } } /** * Assert that a given soap action is called with optional arguments. * * @param string $action * @return void */ public function assertActionCalled(string $action) { $this->assertSent(function (Request $request) use ($action) { return $request->action() === $action; }); } /** * Assert that a request / response pair was recorded matching a given truth test. * * @param callable $callback * @return void */ public function assertSent($callback) { PHPUnit::assertTrue( $this->factory->recorded($callback)->count() > 0, 'An expected request was not recorded.' ); } /** * Assert how many requests have been recorded. */ public function assertSentCount(int $count): void { PHPUnit::assertCount($count, $this->factory->getRecorded()); } /** * Assert that the given request was sent in the given order. * * @param array $callbacks * @return void */ public function assertSentInOrder($callbacks) { $this->assertSentCount(count($callbacks)); foreach ($callbacks as $index => $url) { $callback = is_callable($url) ? $url : function ($request) use ($url) { return $request->url() == $url; }; PHPUnit::assertTrue($callback( $this->factory->getRecorded()[$index][0], $this->factory->getRecorded()[$index][1] ), 'An expected request (#'.($index + 1).') was not recorded.'); } } } ================================================ FILE: src/Xml/XMLSerializer.php ================================================ nodeType) { case XML_CDATA_SECTION_NODE: case XML_TEXT_NODE: $output = trim($node->textContent); break; case XML_ELEMENT_NODE: for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { $child = $node->childNodes->item($i); $v = self::domNodeToArray($child); if (isset($child->tagName)) { $t = Str::after($child->tagName, ':'); if (! isset($output[$t])) { $output[$t] = []; } $output[$t][] = $v; } elseif ($v || $v === '0') { $output = is_array($v) ? json_encode($v) : $v; } } if ($node->attributes->length && ! is_array($output)) { // Has attributes but isn't an array $output = ['@content' => $output]; // Change output into an array. } if (is_array($output)) { if ($node->attributes->length) { $a = []; foreach ($node->attributes as $attrName => $attrNode) { $a[$attrName] = (string) $attrNode->value; } $output['@attributes'] = $a; } foreach ($output as $t => $v) { if (is_array($v) && count($v) == 1 && $t != '@attributes') { $output[$t] = $v[0]; } } } break; } return $output; } /** * Return a valid SOAP Xml. * * @param array $array * @return mixed */ public static function arrayToSoapXml(array $array) { $array = [ 'SOAP-ENV:Body' => $array, ]; $xml = new SimpleXMLElement(''); self::addArrayToXml($array, $xml); return $xml->asXML(); } public static function addArrayToXml(array $array, &$xml) { foreach ($array as $key => $value) { if (is_array($value)) { if (is_int($key)) { $key = 'node'; } $label = $xml->addChild($key); self::addArrayToXml($value, $label); } else { $xml->addChild($key, $value); } } } } ================================================ FILE: tests/Fixtures/CustomSoapClient.php ================================================ baseWsdl(__DIR__.'/Wsdl/weather.wsdl'); $this->withGuzzleClientOptions([ 'handler' => $this->buildHandlerStack(), ]); $this->refreshEngine(); $this->isClientBuilded = true; return $this; } } ================================================ FILE: tests/Fixtures/Responses/SoapFault.xml ================================================ soap:VersionMismatch Message was not SOAP 1.1 compliant http://sample.org.ocm/jws/authnticator ================================================ FILE: tests/Fixtures/Wsdl/weather.wsdl ================================================ Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only Gets Information for each WeatherID Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only Allows you to get your City's Weather, which is updated hourly. U.S. Only ================================================ FILE: tests/TestCase.php ================================================ Soap::class, ]; } /** * Define environment setup. * * @param \Illuminate\Foundation\Application $app * @return void */ protected function getEnvironmentSetUp($app) { // Setup default wsse $app['config']->set('soap.clients.laravel_soap', [ 'base_wsdl' => __DIR__.'/Fixtures/Wsdl/weather.wsdl', 'with_wsse' => [ 'user_token_name' => 'username', 'user_token_password' => 'password', ], ]); $app['config']->set('soap.code.path', __DIR__.'/app'); $app['config']->set('soap.code.namespace', 'App\\Soap'); } } ================================================ FILE: tests/Unit/Client/ResponseTest.php ================================================ body()); } } ================================================ FILE: tests/Unit/Commands/MakeClientCommandTest.php ================================================ markTestSkipped('wsdl2Php package has been removed. Needs refactoring'); $this->artisan('soap:make:client --dry-run') ->expectsQuestion('Please type the wsdl or the name of your client configuration if u have defined one in the config "soap.php"', 'laravel_soap'); } } ================================================ FILE: tests/Unit/Commands/MakeValidationCommandTest.php ================================================ markTestSkipped('wsdl2Php package has been removed. Needs refactoring'); $this->artisan('soap:make:validation --dry-run') ->expectsQuestion('Please type the wsdl or the name of your client configuration if u have defined one in the config "soap.php"', 'laravel_soap') ->expectsConfirmation('Do you want to generate for every client method a validation?', 'no') ->expectsQuestion('Which method do you want to generate?', 'GetWeatherInformation'); } } ================================================ FILE: tests/Unit/Middleware/CisDhlMiddlewareTest.php ================================================ baseWsdl(dirname(__DIR__, 2).'/Fixtures/Wsdl/weather.wsdl'); $response = $client->call('GetWeatherInformation'); Soap::assertSent(function (Request $request) { return Str::contains($request->xmlContent(), 'ok()); } } ================================================ FILE: tests/Unit/SoapClientTest.php ================================================ call('GetWeatherInformation'); self::assertTrue($response->ok()); Soap::assertSent(function (Request $request) { return $request->action() === 'GetWeatherInformation'; }); Soap::assertNotSent(function (Request $request) { return $request->action() === 'GetCityWeatherByZIPSoapOut'; }); Soap::assertActionCalled('GetWeatherInformation'); } public function testMagicCallByConfig() { Soap::fake(); Event::fake(); $response = Soap::buildClient('laravel_soap')->GetWeatherInformation(); self::assertTrue($response->ok()); } public function testWsseWithWsaCall() { Soap::fake(); ray()->showSoapClientRequests(); $client = Soap::baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl')->withWsse([ 'userTokenName' => 'Test', 'userTokenPassword' => 'passwordTest', 'mustUnderstand' => false, ])->withWsa(); $response = $client->GetWeatherInformation(); Soap::assertSent(function (Request $request) { return ! Str::contains($request->xmlContent(), 'mustUnderstand'); }); self::assertTrue($response->ok()); } public function testWsseWithWsa2005Call() { Soap::fake(); ray()->showSoapClientRequests(); $client = Soap::baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl')->withWsse([ 'userTokenName' => 'Test', 'userTokenPassword' => 'passwordTest', 'mustUnderstand' => false, ])->withWsa2005(); $response = $client->GetWeatherInformation(); Soap::assertSent(function (Request $request) { return ! Str::contains($request->xmlContent(), 'mustUnderstand'); }); self::assertTrue($response->ok()); } public function testArrayAccessResponse() { Soap::fakeSequence()->push('test'); Event::fake(); $response = Soap::buildClient('laravel_soap')->GetWeatherInformation()['response']; self::assertEquals('test', $response); } public function testRequestWithArguments() { Soap::fake(); Event::fake(); $arguments = [ 'prename' => 'Corona', 'lastname' => 'Pandemic', ]; /** @var Response $response */ $response = Soap::buildClient('laravel_soap')->Submit_User($arguments); Event::assertDispatched(RequestSending::class); Event::assertDispatched(ResponseReceived::class); self::assertTrue($response->ok()); Soap::assertSent(function (Request $request) use ($arguments) { return $request->arguments() === $arguments && $request->action() === 'Submit_User'; }); } public function testSequenceFake() { $responseFake = ['user' => 'test']; $responseFake2 = ['user' => 'test2']; Event::fake(); Soap::fakeSequence() ->push($responseFake) ->whenEmpty(Soap::response($responseFake2)); $client = Soap::buildClient('laravel_soap'); $response = $client->Get_User(); $response2 = $client->Get_User(); $response3 = $client->Get_User(); self::assertTrue($response->ok()); self::assertEquals($responseFake, $response->json()); self::assertEquals($responseFake2, $response2->json()); self::assertEquals($responseFake2, $response3->json()); Soap::assertSentCount(3); } /** * @dataProvider soapActionProvider * * @param $action * @param $fake * @param $exspected */ public function testSoapFake($action, $fake, $exspected) { $fake = collect($fake)->map(function ($item) { return Soap::response($item); })->all(); Soap::fake($fake); Event::fake(); Event::assertNotDispatched(RequestSending::class); Event::assertNotDispatched(ResponseReceived::class); $response = Soap::baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl') ->call($action); Event::assertDispatched(RequestSending::class); Event::assertDispatched(ResponseReceived::class); Event::assertNotDispatched(ConnectionFailed::class); self::assertEquals($exspected, $response->json()); } #[ArrayShape([ 'without_fake_array' => 'array', 'with_fake_array_wrong_method' => 'array', 'with_fake_array' => 'array', 'with_fake_string' => 'array', ])] public static function soapActionProvider(): array { $fakeResponse = [ 'GetWeatherInformation' => [ 'Response_Data' => [ 'Users' => [ [ 'name' => 'test', 'field' => 'bla', ], ], ], ], 'GetCityForecastByZIP' => 'Test', ]; return [ 'without_fake_array' => ['GetCityWeatherByZIP', null, null], 'with_fake_array_wrong_method' => ['GetCityWeatherByZIP', $fakeResponse, null], 'with_fake_array' => ['GetWeatherInformation', $fakeResponse, $fakeResponse['GetWeatherInformation']], 'with_fake_string' => ['GetCityForecastByZIP', $fakeResponse, ['response' => 'Test']], ]; } public function testSoapOptions(): void { Soap::fake(); Event::fake(); $client = Soap::withOptions(['soap_version' => SOAP_1_2]) ->baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl'); $response = $client->call('GetWeatherInformation'); self::assertTrue($response->ok()); Soap::assertSent(function (Request $request) { return Str::contains( $request->getRequest()->getHeaderLine('Content-Type'), 'application/soap+xml; charset="utf-8"' ); }); Soap::assertActionCalled('GetWeatherInformation'); } public function testRealSoapCall(): void { $this->markTestSkipped('Real Soap Call Testing. Comment the line out for testing'); ray()->showSoapClientRequests(); // location has to be set because the wsdl has a wrong location declaration $client = Soap::baseWsdl('https://www.w3schools.com/xml/tempconvert.asmx?wsdl') ->withOptions([ 'soap_version' => SOAP_1_2, 'location' => 'https://www.w3schools.com/xml/tempconvert.asmx?wsdl', ]); $result = $client->call('FahrenheitToCelsius', [ 'Fahrenheit' => 75, ]); self::assertArrayHasKey('FahrenheitToCelsiusResult', $result->json()); $result = $client->FahrenheitToCelsius([ 'Fahrenheit' => 75, ]); self::assertArrayHasKey('FahrenheitToCelsiusResult', $result->json()); } public function testRealSoapCallBank(): void { $this->markTestSkipped('Real Soap Call Testing. Comment the line out for testing'); ray()->showSoapClientRequests(); // location has to be set because the wsdl has a wrong location declaration $client = Soap::baseWsdl('http://www.thomas-bayer.com/axis2/services/BLZService?wsdl') ->withOptions([ 'soap_version' => SOAP_1_2, 'location' => 'http://www.thomas-bayer.com/axis2/services/BLZService?wsdl', ]); $result = $client->call('getBank', [ 'blz' => '74120071', ]); dd($result->json()); self::assertArrayHasKey('FahrenheitToCelsiusResult', $result->json()); $result = $client->FahrenheitToCelsius([ 'Fahrenheit' => 75, ]); self::assertArrayHasKey('FahrenheitToCelsiusResult', $result->json()); } /** * @dataProvider soapHeaderProvider * * @param $header * @param $exspected */ public function testSoapWithDifferentHeaders($header, $exspected): void { Soap::fake(); Event::fake(); $client = Soap::withHeaders($header)->baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl'); $response = $client->call('GetWeatherInformation'); Soap::assertSent(function (Request $request) use ($exspected) { return $request->getRequest()->getHeaderLine('test') === $exspected; }); self::assertTrue($response->ok()); Soap::assertActionCalled('GetWeatherInformation'); } public function testArgumentsCanBeCalledTwice(): void { Soap::fake(); Event::fake(); Soap::assertNothingSent(); $response = Soap::baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl') ->call('GetWeatherInformation'); self::assertTrue($response->ok()); Soap::assertSent(function (Request $request) { return $request->arguments() === $request->arguments(); }); } public static function soapHeaderProvider(): array { $header = [ 'test' => 'application/soap+xml; charset="utf-8"', ]; return [ 'without_header' => [[], ''], 'with_header' => [$header, $header['test']], ]; } public function testSoapClientClassMayBeCustomized(): void { Soap::fake(); Event::fake(); $client = Soap::buildClient('laravel_soap'); $this->assertInstanceOf(SoapClient::class, $client); SoapFactory::useClientClass(CustomSoapClient::class); $client = Soap::buildClient('laravel_soap'); $this->assertInstanceOf(CustomSoapClient::class, $client); } public function testHandlerOptions(): void { Soap::fake(); Event::fake(); $client = Soap::baseWsdl(dirname(__DIR__, 1).'/Fixtures/Wsdl/weather.wsdl'); $response = $client->call('GetWeatherInformation'); self::assertTrue($response->ok()); self::assertEquals(true, $client->getClient()->getConfig()['verify']); $client = $client->withGuzzleClientOptions([ 'allow_redirects' => RedirectMiddleware::$defaultSettings, 'http_errors' => true, 'decode_content' => true, 'verify' => false, 'cookies' => false, 'idn_conversion' => false, ]); $response = $client->call('GetWeatherInformation'); self::assertTrue($response->ok()); self::assertEquals(false, $client->getClient()->getConfig()['verify']); } } ================================================ FILE: tests/Unit/Xml/XmlSerializerTest.php ================================================ Code dredd XML; protected $array = [ 'prename' => 'Code', 'lastname' => 'dredd', ]; public function testArrayToSoapXml() { $soapXml = XMLSerializer::arrayToSoapXml($this->array); self::assertXmlStringEqualsXmlString($this->xml, $soapXml); } }