SoapClient large refactoring & tests update

This commit is contained in:
Petr Bechyně 2017-02-03 15:22:37 +01:00
parent 00ddf149b0
commit aee034791e
78 changed files with 4957 additions and 1126 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/vendor/ /vendor/
composer.lock
phpunit.xml phpunit.xml
.idea/

102
README.md
View File

@ -1,35 +1,36 @@
# BeSimpleSoap # BeSimpleSoap
Build SOAP and WSDL based web services Build SOAP and WSDL based web services.
This fork from 2017 is a refactored version that fixes a lot of errors and provides
better APi, more robust, stable and modern codebase.
See [How to use](#how-to-use) that will help you to understand the magic.
# Components # Components
BeSimpleSoap consists of five components ... BeSimpleSoap consists of five components ...
## BeSimpleSoapBundle
The BeSimpleSoapBundle is a Symfony2 bundle to build WSDL and SOAP based web services.
For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapBundle/README.md).
## BeSimpleSoapClient ## BeSimpleSoapClient
The BeSimpleSoapClient is a component that extends the native PHP SoapClient with further features like SwA, MTOM and WS-Security. **Refactored** BeSimpleSoapClient is a component that extends the native PHP SoapClient with further features like SwA and WS-Security.
For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapClient/README.md).
## BeSimpleSoapCommon
The BeSimpleSoapCommon component contains functionylity shared by both the server and client implementations.
For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapCommon/README.md).
## BeSimpleSoapServer ## BeSimpleSoapServer
The BeSimpleSoapServer is a component that extends the native PHP SoapServer with further features like SwA, MTOM and WS-Security. **Refactored** BeSimpleSoapServer is a component that extends the native PHP SoapServer with further features like SwA and WS-Security.
For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapServer/README.md).
## BeSimpleSoapCommon
**Refactored** BeSimpleSoapCommon component contains functionality shared by both the server and client implementations.
## BeSimpleSoapWsdl ## BeSimpleSoapWsdl
For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapWsdl/README.md). Currently **unsupported!** For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapWsdl/README.md).
## BeSimpleSoapBundle
Currently **unsupported!**
The BeSimpleSoapBundle is a Symfony2 bundle to build WSDL and SOAP based web services.
For further information see the the original [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapBundle/README.md).
*May not work properly since the Symfony libraries were removed.*
# Installation # Installation
@ -54,3 +55,70 @@ Now you are ready to install the library:
```sh ```sh
php /usr/local/bin/composer.phar install php /usr/local/bin/composer.phar install
``` ```
# How to use
You can investigate the unit tests in order to get a clue.
Forget about associative arrays, multiple extension and silent errors!
```
// unit tests for soap client
BeSimple\SoapClient\Tests\SoapClientBuilderTest
// unit tests for soap server
BeSimple\SoapServer\Tests\SoapServerBuilderTest
```
## Small example of soap client call
```php
$soapClientBuilder = new SoapClientBuilder();
$soapClient = $soapClientBuilder->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults('http://path/to/wsdlfile.wsdl')
);
$myRequest = new MyRequest();
$myRequest->attribute = 'string value';
$soapResponse = $soapClient->soapCall('myMethod', [$myRequest]);
var_dump($soapResponse); // Contains Response, Attachments
```
### Something wrong?!
Turn on the tracking and catch `SoapFaultWithTracingData` exception to get some sweets :)
```php
try {
$soapResponse = $soapClient->soapCall('GetUKLocationByCounty', [$getUKLocationByCountyRequest]);
} catch (SoapFaultWithTracingData $fault) {
var_dump($fault->getSoapResponseTracingData()->getLastRequest());
}
```
## Small example of soap server handling
Starting a SOAP server is a bit more complex.
I would suggest you to inspect SoapServer unit tests to get complete image.
But don't be scared too much, you just need to create a DummyService that will
handle your client SOAP calls.
```php
$dummyService = new DummyService();
$classMap = new ClassMap();
foreach ($dummyService->getClassMap() as $type => $className) {
$classMap->add($type, $className);
}
$soapServerBuilder = new SoapServerBuilder();
$soapServerOptions = SoapServerOptionsBuilder::createWithDefaults($dummyService);
$soapOptions = SoapOptionsBuilder::createWithClassMap($dummyService->getWsdlPath(), $classMap);
$soapServer = $soapServerBuilder->build($soapServerOptions, $soapOptions);
$request = $soapServer->createRequest(
$dummyService->getEndpoint(),
'DummyService.dummyServiceMethod',
'text/xml;charset=UTF-8',
'<your><soap><request><here /></request></soap></your>'
);
$response = $soapServer->handleRequest($request);
var_dump($response); // Contains Response, Attachments
```

2
cache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -19,16 +19,14 @@
}, },
{ {
"name": "Petr Bechyně", "name": "Petr Bechyně",
"email": "petr.bechyne@vodafone.com" "email": "mail@petrbechyne.com"
} }
], ],
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0|>=7.0",
"ext-soap": "*", "ext-soap": "*",
"ext-curl": "*", "ext-curl": "*",
"ass/xmlsecurity": "~1.0", "ass/xmlsecurity": "~1.0",
"symfony/framework-bundle": "~2.6|~3.0",
"symfony/twig-bundle": "~2.6|~3.0",
"zendframework/zend-mime": "2.1.*" "zendframework/zend-mime": "2.1.*"
}, },
"replace": { "replace": {
@ -41,8 +39,7 @@
"require-dev": { "require-dev": {
"ext-mcrypt": "*", "ext-mcrypt": "*",
"mikey179/vfsStream": "~1.0", "mikey179/vfsStream": "~1.0",
"symfony/filesystem": "~2.3", "phpunit/phpunit": "~4.0"
"symfony/process": "~2.3"
}, },
"autoload": { "autoload": {
"psr-0": { "BeSimple\\": "src/" } "psr-0": { "BeSimple\\": "src/" }

1349
composer.lock generated Normal file
View File

@ -0,0 +1,1349 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "7601f1ec280c086361e1deaf021f8ca1",
"content-hash": "59372f34124af17d5c35938dfffc6075",
"packages": [
{
"name": "ass/xmlsecurity",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/aschamberger/XmlSecurity.git",
"reference": "c8976519ebbf6e4d953cd781d09df44b7f65fbb8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aschamberger/XmlSecurity/zipball/c8976519ebbf6e4d953cd781d09df44b7f65fbb8",
"reference": "c8976519ebbf6e4d953cd781d09df44b7f65fbb8",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"lib-openssl": ">=0.9.0",
"php": ">=5.3.0"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master"
},
"suggest": {
"ext-mcrypt": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"ass\\XmlSecurity": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Robert Richards",
"email": "rrichards@cdatazone.org"
},
{
"name": "Andreas Schamberger",
"email": "mail@andreass.net"
}
],
"description": "The XmlSecurity library is written in PHP for working with XML Encryption and Signatures",
"homepage": "https://github.com/aschamberger/XmlSecurity",
"keywords": [
"encryption",
"security",
"signature",
"xml"
],
"time": "2015-05-31 10:10:35"
},
{
"name": "zendframework/zend-mime",
"version": "2.1.6",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-mime.git",
"reference": "066d6eecff586a7fb10e8907c032beaf1a9d6104"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-mime/zipball/066d6eecff586a7fb10e8907c032beaf1a9d6104",
"reference": "066d6eecff586a7fb10e8907c032beaf1a9d6104",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"zendframework/zend-stdlib": "self.version"
},
"require-dev": {
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master",
"zendframework/zend-mail": "self.version"
},
"suggest": {
"zendframework/zend-mail": "Zend\\Mail component"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev",
"dev-develop": "2.2-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Mime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"homepage": "https://github.com/zendframework/zend-mime",
"keywords": [
"mime",
"zf2"
],
"time": "2013-04-17 13:32:54"
},
{
"name": "zendframework/zend-stdlib",
"version": "2.1.6",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-stdlib.git",
"reference": "0027339961ad3d49f91ee092e23f7269c18cb470"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/0027339961ad3d49f91ee092e23f7269c18cb470",
"reference": "0027339961ad3d49f91ee092e23f7269c18cb470",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master",
"zendframework/zend-eventmanager": "self.version",
"zendframework/zend-filter": "self.version",
"zendframework/zend-serializer": "self.version",
"zendframework/zend-servicemanager": "self.version"
},
"suggest": {
"pecl-weakref": "Implementation of weak references for Stdlib\\CallbackHandler",
"zendframework/zend-eventmanager": "To support aggregate hydrator usage",
"zendframework/zend-filter": "To support naming strategy hydrator usage",
"zendframework/zend-serializer": "Zend\\Serializer component",
"zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev",
"dev-develop": "2.2-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Stdlib\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"homepage": "https://github.com/zendframework/zend-stdlib",
"keywords": [
"stdlib",
"zf2"
],
"time": "2013-04-17 13:32:54"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14 21:17:01"
},
{
"name": "mikey179/vfsStream",
"version": "v1.6.4",
"source": {
"type": "git",
"url": "https://github.com/mikey179/vfsStream.git",
"reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592",
"reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"org\\bovigo\\vfs\\": "src/main/php"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Frank Kleine",
"homepage": "http://frankkleine.de/",
"role": "Developer"
}
],
"description": "Virtual file system to mock the real file system in unit tests.",
"homepage": "http://vfs.bovigo.org/",
"time": "2016-07-18 14:02:57"
},
{
"name": "phpdocumentor/reflection-common",
"version": "1.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
"reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jaap van Otterdijk",
"email": "opensource@ijaap.nl"
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
"phpdoc",
"reflection",
"static analysis"
],
"time": "2015-12-27 11:43:31"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
"reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
"shasum": ""
},
"require": {
"php": ">=5.5",
"phpdocumentor/reflection-common": "^1.0@dev",
"phpdocumentor/type-resolver": "^0.2.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^4.4"
},
"type": "library",
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2016-09-30 07:12:33"
},
{
"name": "phpdocumentor/type-resolver",
"version": "0.2.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
"reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
"shasum": ""
},
"require": {
"php": ">=5.5",
"phpdocumentor/reflection-common": "^1.0"
},
"require-dev": {
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^5.2||^4.8.24"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"time": "2016-11-25 06:54:22"
},
{
"name": "phpspec/prophecy",
"version": "v1.6.2",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb",
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
"sebastian/comparator": "^1.1",
"sebastian/recursion-context": "^1.0|^2.0"
},
"require-dev": {
"phpspec/phpspec": "^2.0",
"phpunit/phpunit": "^4.8 || ^5.6.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"time": "2016-11-21 14:58:47"
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "~1.3",
"sebastian/environment": "^1.3.2",
"sebastian/version": "~1.0"
},
"require-dev": {
"ext-xdebug": ">=2.1.4",
"phpunit/phpunit": "~4"
},
"suggest": {
"ext-dom": "*",
"ext-xdebug": ">=2.2.1",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2015-10-06 15:47:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2016-10-03 07:40:28"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21 13:50:34"
},
{
"name": "phpunit/php-timer",
"version": "1.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4|~5"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"time": "2016-05-12 18:03:57"
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b",
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"time": "2016-11-15 14:06:22"
},
{
"name": "phpunit/phpunit",
"version": "4.8.34",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7eb45205d27edd94bd2b3614085ea158bd1e2bca",
"reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.2.2",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
"symfony/yaml": "~2.1|~3.0"
},
"suggest": {
"phpunit/php-invoker": "~1.1"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.8.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2017-01-26 16:15:36"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"time": "2015-10-02 06:51:40"
},
{
"name": "sebastian/comparator",
"version": "1.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
"reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2 || ~2.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2017-01-29 09:50:25"
},
{
"name": "sebastian/diff",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2015-12-08 07:14:41"
},
{
"name": "sebastian/environment",
"version": "1.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
"reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"time": "2016-08-18 05:49:44"
},
{
"name": "sebastian/exporter",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"ext-mbstring": "*",
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2016-06-17 09:04:28"
},
{
"name": "sebastian/global-state",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2015-10-12 03:26:01"
},
{
"name": "sebastian/recursion-context",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-11-11 19:50:13"
},
{
"name": "sebastian/version",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2015-06-21 13:59:46"
},
{
"name": "symfony/yaml",
"version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "50eadbd7926e31842893c957eca362b21592a97d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/50eadbd7926e31842893c957eca362b21592a97d",
"reference": "50eadbd7926e31842893c957eca362b21592a97d",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"require-dev": {
"symfony/console": "~2.8|~3.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2017-01-03 13:51:32"
},
{
"name": "webmozart/assert",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"time": "2016-11-23 20:04:58"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0|>=7.0",
"ext-soap": "*",
"ext-curl": "*"
},
"platform-dev": {
"ext-mcrypt": "*"
}
}

View File

@ -9,6 +9,7 @@
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
syntaxCheck="false" syntaxCheck="false"
stderr="true"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
> >
@ -18,17 +19,8 @@
<testsuites> <testsuites>
<testsuite name="BeSimpleSoap Test Suite"> <testsuite name="BeSimpleSoap Test Suite">
<directory>./src/BeSimple/*/Tests/</directory> <file>src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php</file>
<file>src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php</file>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter>
<whitelist>
<directory>./src/BeSimple/</directory>
<exclude>
<directory>./src/BeSimple/*/Tests</directory>
<directory>./src/BeSimple/*/Resources</directory>
</exclude>
</whitelist>
</filter>
</phpunit> </phpunit>

View File

@ -13,27 +13,45 @@
namespace BeSimple\SoapBundle; namespace BeSimple\SoapBundle;
use BeSimple\SoapCommon\Cache as BaseCache; use BeSimple\SoapCommon\Cache as BaseCache;
use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use Exception;
/** /**
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
*/ */
class Cache class Cache
{ {
public function __construct($cacheDisabled, $type, $directory, $lifetime = null, $limit = null) public function __construct(SoapOptions $soapOptions)
{ {
$isEnabled = (Boolean) $cacheDisabled ? BaseCache::DISABLED : BaseCache::ENABLED; if ($soapOptions->isWsdlCached()) {
$isEnabled = (bool)$soapOptions->isWsdlCached() ? BaseCache::ENABLED : BaseCache::DISABLED;
BaseCache::setEnabled($isEnabled); BaseCache::setEnabled($isEnabled);
BaseCache::setType($soapOptions->getWsdlCacheType());
BaseCache::setType($type); BaseCache::setDirectory($soapOptions->getWsdlCacheDir());
BaseCache::setDirectory($directory); } else {
BaseCache::setEnabled(BaseCache::DISABLED);
if (null !== $lifetime) { BaseCache::setType(SoapOptions::SOAP_CACHE_TYPE_NONE);
BaseCache::setLifetime($lifetime); BaseCache::setDirectory(null);
} }
}
if (null !== $limit) { public function validateSettings(SoapOptions $soapOptions)
BaseCache::setLimit($limit); {
if ($soapOptions->isWsdlCached()) {
if (BaseCache::isEnabled() !== true) {
throw new Exception('WSDL cache could not be set');
}
if ($soapOptions->getWsdlCacheType() !== (int)BaseCache::getType()) {
throw new Exception('WSDL cache type could not be set, ini settings is: '.BaseCache::getType());
}
if ($soapOptions->getWsdlCacheDir() !== BaseCache::getDirectory()) {
throw new Exception('WSDL cache dir could not be set, real dir is: '.BaseCache::getDirectory());
}
} else {
if (BaseCache::isEnabled() !== false) {
throw new Exception('WSDL cache could not be turned off');
}
} }
} }
} }

View File

@ -0,0 +1,36 @@
<?php
namespace BeSimple\SoapBundle\Soap;
class SoapAttachmentList
{
private $soapAttachments;
/**
* @param SoapAttachment[] $soapAttachments
*/
public function __construct(array $soapAttachments = [])
{
$this->soapAttachments = $soapAttachments;
}
public function hasSoapAttachments()
{
return $this->soapAttachments !== null && count($this->soapAttachments) > 0;
}
public function getSoapAttachments()
{
return $this->soapAttachments;
}
public function getSoapAttachmentIds()
{
$ids = [];
foreach ($this->getSoapAttachments() as $soapAttachment) {
$ids[] = $soapAttachment->getId();
}
return $ids;
}
}

View File

@ -13,6 +13,8 @@ namespace BeSimple\SoapBundle;
use BeSimple\SoapBundle\ServiceBinding\ServiceBinder; use BeSimple\SoapBundle\ServiceBinding\ServiceBinder;
use BeSimple\SoapCommon\Converter\TypeConverterCollection; use BeSimple\SoapCommon\Converter\TypeConverterCollection;
use BeSimple\SoapCommon\SoapOptionsBuilder;
use BeSimple\SoapServer\SoapServerOptionsBuilder;
use BeSimple\SoapWsdl\Dumper\Dumper; use BeSimple\SoapWsdl\Dumper\Dumper;
use BeSimple\SoapServer\SoapServerBuilder; use BeSimple\SoapServer\SoapServerBuilder;
use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\ConfigCache;
@ -102,15 +104,14 @@ class WebServiceContext
public function getServerBuilder() public function getServerBuilder()
{ {
if (null === $this->serverBuilder) { if (null === $this->serverBuilder) {
$this->serverBuilder = SoapServerBuilder::createWithDefaults() $soapServerBuilder = new SoapServerBuilder();
->withWsdl($this->getWsdlFile()) $this->serverBuilder = $soapServerBuilder->build(
->withClassmap($this->getServiceDefinition()->getTypeRepository()->getClassmap()) SoapServerOptionsBuilder::createWithDefaults(),
->withTypeConverters($this->converters) SoapOptionsBuilder::createWithClassMap(
; $this->getWsdlFile(),
$this->getServiceDefinition()->getTypeRepository()->getClassmap()
if (null !== $this->options['cache_type']) { )
$this->serverBuilder->withWsdlCache($this->options['cache_type']); );
}
} }
return $this->serverBuilder; return $this->serverBuilder;

View File

@ -1,336 +0,0 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapClient;
/**
* cURL wrapper class for doing HTTP requests that uses the soap class options.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class Curl
{
/**
* HTTP User Agent.
*
* @var string
*/
const USER_AGENT = 'PHP-SOAP/\BeSimple\SoapClient';
/**
* Curl resource.
*
* @var resource
*/
private $ch;
/**
* Maximum number of location headers to follow.
*
* @var int
*/
private $followLocationMaxRedirects;
/**
* Request response data.
*
* @var string
*/
private $response;
/**
* Constructor.
*
* @param array $options Options array from SoapClient constructor
* @param int $followLocationMaxRedirects Redirection limit for Location header
*/
public function __construct(array $options = [], $followLocationMaxRedirects = 10)
{
// set the default HTTP user agent
if (!isset($options['user_agent'])) {
$options['user_agent'] = self::USER_AGENT;
}
$this->followLocationMaxRedirects = $followLocationMaxRedirects;
// make http request
$this->ch = curl_init();
$curlOptions = array(
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FAILONERROR => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HEADER => true,
CURLOPT_USERAGENT => $options['user_agent'],
CURLINFO_HEADER_OUT => true,
);
curl_setopt_array($this->ch, $curlOptions);
if (isset($options['compression']) && !($options['compression'] & SOAP_COMPRESSION_ACCEPT)) {
curl_setopt($this->ch, CURLOPT_ENCODING, 'identity');
}
if (isset($options['connection_timeout'])) {
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $options['connection_timeout']);
}
if (isset($options['proxy_host'])) {
if (false !== $options['proxy_host']) {
$proxyHost = $options['proxy_host'].(isset($options['proxy_port']) ? $options['proxy_port'] : 8080);
} else {
$proxyHost = false;
}
curl_setopt($this->ch, CURLOPT_PROXY, $proxyHost);
if (false !== $proxyHost && isset($options['proxy_login'])) {
curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $options['proxy_login'].':'.$options['proxy_password']);
if (isset($options['proxy_auth'])) {
curl_setopt($this->ch, CURLOPT_PROXYAUTH, $options['proxy_auth']);
}
}
}
if (isset($options['login'])) {
curl_setopt($this->ch, CURLOPT_HTTPAUTH, isset($options['extra_options']['http_auth']) ? $options['extra_options']['http_auth'] : CURLAUTH_ANY);
curl_setopt($this->ch, CURLOPT_USERPWD, $options['login'].':'.$options['password']);
}
if (isset($options['local_cert'])) {
curl_setopt($this->ch, CURLOPT_SSLCERT, $options['local_cert']);
curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $options['passphrase']);
}
if (isset($options['ca_info'])) {
curl_setopt($this->ch, CURLOPT_CAINFO, $options['ca_info']);
}
if (isset($options['ca_path'])) {
curl_setopt($this->ch, CURLOPT_CAPATH, $options['ca_path']);
}
}
/**
* Destructor.
*/
public function __destruct()
{
curl_close($this->ch);
}
/**
* Execute HTTP request.
* Returns true if request was successful.
*
* @param string $location HTTP location
* @param string $request Request body
* @param array $requestHeaders Request header strings
* @param array $requestOptions An array of request options
*
* @return bool
*/
public function exec($location, $request = null, $requestHeaders = array(), $requestOptions = array())
{
curl_setopt($this->ch, CURLOPT_URL, $location);
if (!is_null($request)) {
curl_setopt($this->ch, CURLOPT_POST, true);
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request);
}
if (count($requestHeaders) > 0) {
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $requestHeaders);
}
if (count($requestOptions) > 0) {
curl_setopt_array($this->ch, $requestOptions);
}
$this->response = $this->execManualRedirect();
return ($this->response === false) ? false : true;
}
/**
* Custom curl_exec wrapper that allows to follow redirects when specific
* http response code is set. SOAP only allows 307.
*
* @param int $redirects Current redirection count
*
* @return mixed
*/
private function execManualRedirect($redirects = 0)
{
if ($redirects > $this->followLocationMaxRedirects) {
// TODO Redirection limit reached, aborting
return false;
}
curl_setopt($this->ch, CURLOPT_HEADER, true);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($this->ch);
$httpResponseCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if ($httpResponseCode == 307) {
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $headerSize);
$matches = array();
preg_match('/Location:(.*?)\n/', $header, $matches);
$url = trim(array_pop($matches));
// @parse_url to suppress E_WARNING for invalid urls
if (($url = @parse_url($url)) !== false) {
$lastUrl = parse_url(curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL));
if (!isset($url['scheme'])) {
$url['scheme'] = $lastUrl['scheme'];
}
if (!isset($url['host'])) {
$url['host'] = $lastUrl['host'];
}
if (!isset($url['path'])) {
$url['path'] = $lastUrl['path'];
}
$newUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query'] ? '?' . $url['query'] : '');
curl_setopt($this->ch, CURLOPT_URL, $newUrl);
return $this->execManualRedirect($redirects++);
}
}
return $response;
}
/**
* Error code mapping from cURL error codes to PHP ext/soap error messages
* (where applicable)
*
* http://curl.haxx.se/libcurl/c/libcurl-errors.html
*
* @return array(int=>string)
*/
protected function getErrorCodeMapping()
{
return array(
1 => 'Unknown protocol. Only http and https are allowed.', //CURLE_UNSUPPORTED_PROTOCOL
3 => 'Unable to parse URL', //CURLE_URL_MALFORMAT
5 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_PROXY
6 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_HOST
7 => 'Could not connect to host', //CURLE_COULDNT_CONNECT
9 => 'Could not connect to host', //CURLE_REMOTE_ACCESS_DENIED
28 => 'Failed Sending HTTP SOAP request', //CURLE_OPERATION_TIMEDOUT
35 => 'Could not connect to host', //CURLE_SSL_CONNECT_ERROR
41 => 'Can\'t uncompress compressed response', //CURLE_FUNCTION_NOT_FOUND
51 => 'Could not connect to host', //CURLE_PEER_FAILED_VERIFICATION
52 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_GOT_NOTHING
53 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_NOTFOUND
54 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_SETFAILED
55 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_ERROR
56 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_RECV_ERROR
58 => 'Could not connect to host', //CURLE_SSL_CERTPROBLEM
59 => 'Could not connect to host', //CURLE_SSL_CIPHER
60 => 'Could not connect to host', //CURLE_SSL_CACERT
61 => 'Unknown Content-Encoding', //CURLE_BAD_CONTENT_ENCODING
65 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_FAIL_REWIND
66 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_INITFAILED
67 => 'Could not connect to host', //CURLE_LOGIN_DENIED
77 => 'Could not connect to host', //CURLE_SSL_CACERT_BADFILE
80 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_SSL_SHUTDOWN_FAILED
);
}
/**
* Gets the curl error message.
*
* @return string
*/
public function getErrorMessage()
{
$errorCodeMapping = $this->getErrorCodeMapping();
$errorNumber = curl_errno($this->ch);
if (isset($errorCodeMapping[$errorNumber])) {
return $errorCodeMapping[$errorNumber];
}
return curl_error($this->ch);
}
/**
* Gets the request headers as a string.
*
* @return string
*/
public function getRequestHeaders()
{
return curl_getinfo($this->ch, CURLINFO_HEADER_OUT);
}
/**
* Gets the whole response (including headers) as a string.
*
* @return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Gets the response body as a string.
*
* @return string
*/
public function getResponseBody()
{
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
return substr($this->response, $headerSize);
}
/**
* Gets the response content type.
*
* @return string
*/
public function getResponseContentType()
{
return curl_getinfo($this->ch, CURLINFO_CONTENT_TYPE);
}
/**
* Gets the response headers as a string.
*
* @return string
*/
public function getResponseHeaders()
{
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
return substr($this->response, 0, $headerSize);
}
/**
* Gets the response http status code.
*
* @return string
*/
public function getResponseStatusCode()
{
return curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
}
/**
* Gets the response http status message.
*
* @return string
*/
public function getResponseStatusMessage()
{
preg_match('/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $this->response, $matches);
return trim(array_pop($matches));
}
}

View File

@ -0,0 +1,234 @@
<?php
namespace BeSimple\SoapClient\Curl;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationBasicOptions;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationDigestOptions;
use BeSimple\SoapClient\Curl\Http\SslCertificateOptions;
use Exception;
class Curl
{
const CURL_SUCCESS = true;
const CURL_FAILED = false;
private $curlSession;
private $options;
/**
* @param CurlOptions $options
*/
public function __construct(CurlOptions $options)
{
$this->curlSession = $this->acquireNewCurlSession($options);
$this->options = $options;
}
public function __destruct()
{
$this->closeCurlSession($this->curlSession);
}
/**
* @param string $location HTTP location
* @param string $request Request body
* @param array $requestHeaders Request header strings
* @return CurlResponse
*/
public function executeCurlWithCachedSession($location, $request = null, $requestHeaders = [])
{
return $this->executeCurlSession($this->curlSession, $this->options, $location, $request, $requestHeaders);
}
/**
* @param CurlOptions $options
* @param string $location HTTP location
* @param string $request Request body
* @param array $requestHeaders Request header strings
* @return CurlResponse
*/
public function executeCurl(CurlOptions $options, $location, $request = null, $requestHeaders = [])
{
$curlSession = $this->acquireNewCurlSession($options);
$curlResponse = $this->executeCurlSession($curlSession, $options, $location, $request, $requestHeaders);
$this->closeCurlSession($curlSession);
return $curlResponse;
}
private function acquireNewCurlSession(CurlOptions $options)
{
$curlSession = curl_init();
curl_setopt_array($curlSession, [
CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HEADER => true,
CURLOPT_USERAGENT => $options->getUserAgent(),
CURLINFO_HEADER_OUT => true,
CURLOPT_CONNECTTIMEOUT => $options->getConnectionTimeout()
]);
return $curlSession;
}
private function closeCurlSession($curlSession)
{
curl_close($curlSession);
}
/**
* @param mixed $curlSession Result of curl_init() handle
* @param CurlOptions $options
* @param string $location HTTP location
* @param string $request Request body
* @param array $requestHeaders Request header strings
* @return CurlResponse
*/
private function executeCurlSession($curlSession, CurlOptions $options, $location, $request = null, $requestHeaders = [])
{
curl_setopt($curlSession, CURLOPT_URL, $location);
curl_setopt($curlSession, CURLOPT_HEADER, true);
curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, true);
if (!is_null($request)) {
curl_setopt($curlSession, CURLOPT_POST, true);
curl_setopt($curlSession, CURLOPT_POSTFIELDS, $request);
} else {
curl_setopt($curlSession, CURLOPT_POST, false);
}
if (count($requestHeaders) > 0) {
curl_setopt($curlSession, CURLOPT_HTTPHEADER, $requestHeaders);
}
if (!$options->getSoapCompression() & SOAP_COMPRESSION_ACCEPT) {
curl_setopt($curlSession, CURLOPT_ENCODING, 'identity');
}
if ($options->hasProxy()) {
$proxyHost = $options->getProxy()->getHost() . $options->getProxy()->getPort();
curl_setopt($curlSession, CURLOPT_PROXY, $proxyHost);
if ($options->getProxy()->hasCredentials()) {
curl_setopt($curlSession, CURLOPT_PROXYUSERPWD, $options->getProxy()->getLogin() . ':' . $options->getProxy()->getPassword());
if ($options->getProxy()->hasAuthenticationType()) {
curl_setopt($curlSession, CURLOPT_PROXYAUTH, $options->getProxy()->getAuthenticationType());
}
}
}
if ($options->hasHttpAuthentication()) {
if ($options->hasHttpAuthenticationBasic()) {
/** @var HttpAuthenticationBasicOptions $httpAuthenticationBasic */
$httpAuthenticationBasic = $options->getHttpAuthentication();
curl_setopt($curlSession, CURLOPT_HTTPAUTH, $httpAuthenticationBasic->getAuthenticationType());
curl_setopt($curlSession, CURLOPT_USERPWD, $httpAuthenticationBasic->getUsername() . ':' . $httpAuthenticationBasic->getPassword());
} else if ($options->hasHttpAuthenticationDigest()) {
/** @var HttpAuthenticationDigestOptions $httpAuthenticationDigest */
$httpAuthenticationDigest = $options->getHttpAuthentication();
curl_setopt($curlSession, CURLOPT_HTTPAUTH, $httpAuthenticationDigest->getAuthenticationType());
} else {
throw new Exception('Unresolved authentication type: '.get_class($options->getHttpAuthentication()));
}
}
if ($options->hasSslCertificateOptions()) {
$sslCertificateOptions = $options->getSslCertificateOptions();
curl_setopt($curlSession, CURLOPT_SSLCERT, $sslCertificateOptions->getCertificateLocalPath());
if ($sslCertificateOptions->hasCertificatePassPhrase()) {
curl_setopt($curlSession, CURLOPT_SSLCERTPASSWD, $sslCertificateOptions->getCertificatePassPhrase());
}
if ($sslCertificateOptions->hasCertificateAuthorityInfo()) {
curl_setopt($curlSession, CURLOPT_CAINFO, $sslCertificateOptions->getCertificateAuthorityInfo());
}
if ($sslCertificateOptions->hasCertificateAuthorityPath()) {
curl_setopt($curlSession, CURLOPT_CAPATH, $sslCertificateOptions->hasCertificateAuthorityPath());
}
}
$executeSoapCallResponse = $this->executeHttpCall($curlSession, $options);
$httpRequestHeadersAsString = curl_getinfo($curlSession, CURLINFO_HEADER_OUT);
$headerSize = curl_getinfo($curlSession, CURLINFO_HEADER_SIZE);
$httpResponseCode = curl_getinfo($curlSession, CURLINFO_HTTP_CODE);
$httpResponseContentType = curl_getinfo($curlSession, CURLINFO_CONTENT_TYPE);;
preg_match('/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $executeSoapCallResponse, $httpResponseMessages);
$httpResponseMessage = trim(array_pop($httpResponseMessages));
$curlErrorMessage = sprintf(
'Curl error "%s" with message: %s occurred while connecting to %s',
curl_errno($curlSession),
curl_error($curlSession),
$location
);
if ($executeSoapCallResponse === false) {
return new CurlResponse(
$httpRequestHeadersAsString,
$httpResponseCode,
$httpResponseMessage,
$httpResponseContentType,
self::CURL_FAILED,
$curlErrorMessage
);
}
return new CurlResponse(
$httpRequestHeadersAsString,
$httpResponseCode,
$httpResponseMessage,
$httpResponseContentType,
self::CURL_SUCCESS,
null,
substr($executeSoapCallResponse, 0, $headerSize),
substr($executeSoapCallResponse, $headerSize)
);
}
/**
* Custom curl_exec wrapper that allows to follow redirects when specific
* http response code is set. SOAP only allows 307.
*
* @param mixed $curlSession Result of curl_init() handle
* @param CurlOptions $options
* @param int $executedRedirects
* @return string|null
* @throws Exception
*/
private function executeHttpCall($curlSession, CurlOptions $options, $executedRedirects = 0)
{
if ($executedRedirects > $options->getFollowLocationMaxRedirects()) {
throw new Exception('Cannot executeHttpCall - too many redirects: ' . $executedRedirects);
}
$curlExecResponse = curl_exec($curlSession);
$httpResponseCode = curl_getinfo($curlSession, CURLINFO_HTTP_CODE);
if ($httpResponseCode === 307) {
$newUrl = $this->getRedirectUrlFromResponseHeaders($curlSession, $curlExecResponse);
curl_setopt($curlSession, CURLOPT_URL, $newUrl);
return $this->executeHttpCall($curlSession, $options, ++$executedRedirects);
}
return $curlExecResponse;
}
private function getRedirectUrlFromResponseHeaders($curlSession, $curlExecResponse)
{
$curlExecResponseHeaders = substr($curlExecResponse, 0, curl_getinfo($curlSession, CURLINFO_HEADER_SIZE));
$matches = [];
preg_match('/Location:(.*?)\n/', $curlExecResponseHeaders, $matches);
$url = trim(array_pop($matches));
if (($url = @parse_url($url)) !== false) {
$lastUrl = parse_url(curl_getinfo($curlSession, CURLINFO_EFFECTIVE_URL));
if (!isset($url['scheme'])) {
$url['scheme'] = $lastUrl['scheme'];
}
if (!isset($url['host'])) {
$url['host'] = $lastUrl['host'];
}
if (!isset($url['path'])) {
$url['path'] = $lastUrl['path'];
}
return $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query'] ? '?' . $url['query'] : '');
}
throw new Exception('Cannot parse WSDL url redirect: ' . $url);
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace BeSimple\SoapClient\Curl;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationDigestOptions;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationInterface;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationBasicOptions;
use BeSimple\SoapClient\Curl\Http\SslCertificateOptions;
use BeSimple\SoapClient\SoapServerProxy\SoapServerProxy;
class CurlOptions
{
const DEFAULT_USER_AGENT = 'BeSimpleSoap';
const SOAP_COMPRESSION_NONE = null;
const SOAP_COMPRESSION_GZIP = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP;
const SOAP_COMPRESSION_DEFLATE = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_DEFLATE;
private $userAgent;
private $followLocationMaxRedirects;
private $soapCompression;
private $connectionTimeout;
private $proxy;
private $httpAuthentication;
private $sslCertificateOptions;
/**
* @param string $userAgent
* @param int $followLocationMaxRedirects
* @param CurlOptions::SOAP_COMPRESSION_NONE|CurlOptions::SOAP_COMPRESSION_GZIP|CurlOptions::SOAP_COMPRESSION_DEFLATE $soapCompression
* @param int $connectionTimeout
* @param SoapServerProxy|null $proxy
* @param HttpAuthenticationInterface|null $httpAuthentication
* @param SslCertificateOptions|null $sslCertificateOptions
*/
public function __construct(
$userAgent,
$followLocationMaxRedirects,
$soapCompression,
$connectionTimeout,
SoapServerProxy $proxy = null,
HttpAuthenticationInterface $httpAuthentication = null,
SslCertificateOptions $sslCertificateOptions = null
) {
$this->userAgent = $userAgent;
$this->followLocationMaxRedirects = $followLocationMaxRedirects;
$this->soapCompression = $soapCompression;
$this->connectionTimeout = $connectionTimeout;
$this->proxy = $proxy;
$this->httpAuthentication = $httpAuthentication;
$this->sslCertificateOptions = $sslCertificateOptions;
}
public function getUserAgent()
{
return $this->userAgent;
}
public function getFollowLocationMaxRedirects()
{
return $this->followLocationMaxRedirects;
}
public function getSoapCompression()
{
return $this->soapCompression;
}
public function getConnectionTimeout()
{
return $this->connectionTimeout;
}
public function getProxy()
{
return $this->proxy;
}
public function getHttpAuthentication()
{
return $this->httpAuthentication;
}
public function getSslCertificateOptions()
{
return $this->sslCertificateOptions;
}
public function hasProxy()
{
return $this->proxy !== null;
}
public function hasHttpAuthentication()
{
return $this->httpAuthentication !== null;
}
public function hasSslCertificateOptions()
{
return $this->sslCertificateOptions !== null;
}
public function hasHttpAuthenticationBasic()
{
if ($this->hasHttpAuthentication()) {
if ($this->getHttpAuthentication() instanceof HttpAuthenticationBasicOptions) {
return true;
}
}
return false;
}
public function hasHttpAuthenticationDigest()
{
if ($this->hasHttpAuthentication()) {
if ($this->getHttpAuthentication() instanceof HttpAuthenticationDigestOptions) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace BeSimple\SoapClient\Curl;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationDigestOptions;
use BeSimple\SoapClient\Curl\Http\SslCertificateOptions;
use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationBasic;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationDigest;
use BeSimple\SoapClient\Curl\Http\HttpAuthenticationBasicOptions;
use Exception;
class CurlOptionsBuilder
{
const DEFAULT_MAX_REDIRECTS = 10;
const DEFAULT_CONNECTION_TIMEOUT = 10;
public static function buildDefault()
{
return new CurlOptions(
CurlOptions::DEFAULT_USER_AGENT,
self::DEFAULT_MAX_REDIRECTS,
CurlOptions::SOAP_COMPRESSION_NONE,
self::DEFAULT_CONNECTION_TIMEOUT
);
}
public static function buildForSoapClient(SoapClientOptions $soapClientOptions)
{
return new CurlOptions(
$soapClientOptions->getUserAgent(),
self::DEFAULT_MAX_REDIRECTS,
$soapClientOptions->getCompression(),
self::DEFAULT_CONNECTION_TIMEOUT,
$soapClientOptions->getProxy(),
self::getHttpAuthOptions($soapClientOptions),
self::getSslCertificateOptions($soapClientOptions)
);
}
private static function getHttpAuthOptions(SoapClientOptions $soapClientOptions)
{
if ($soapClientOptions->hasAuthentication()) {
if ($soapClientOptions->hasAuthenticationBasic()) {
/** @var SoapServerAuthenticationBasic $basicAuthentication */
$basicAuthentication = $soapClientOptions->getAuthentication();
return new HttpAuthenticationBasicOptions(
$basicAuthentication->getLogin(),
$basicAuthentication->getPassword()
);
} else if ($soapClientOptions->hasAuthenticationDigest()) {
return new HttpAuthenticationDigestOptions();
} else {
throw new Exception('Unresolved authentication type: '.get_class($soapClientOptions->getAuthentication()));
}
}
return null;
}
private static function getSslCertificateOptions(SoapClientOptions $soapClientOptions)
{
if ($soapClientOptions->hasAuthenticationDigest()) {
/** @var SoapServerAuthenticationDigest $digestAuthentication */
$digestAuthentication = $soapClientOptions->getAuthentication();
return new SslCertificateOptions(
$digestAuthentication->getLocalCert(),
$digestAuthentication->getPassPhrase()
);
}
return null;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace BeSimple\SoapClient\Curl;
class CurlResponse
{
private $httpRequestHeaders;
private $httpResponseStatusCode;
private $httpResponseStatusMessage;
private $httpResponseContentType;
private $curlStatus;
private $curlErrorMessage;
private $responseHeader;
private $responseBody;
public function __construct(
$httpRequestHeaders,
$httpResponseStatusCode,
$httpResponseStatusMessage,
$httpResponseContentType,
$curlStatus,
$curlErrorMessage = null,
$responseHeader = null,
$responseBody = null
) {
$this->httpRequestHeaders = $httpRequestHeaders;
$this->httpResponseStatusCode = $httpResponseStatusCode;
$this->httpResponseStatusMessage = $httpResponseStatusMessage;
$this->httpResponseContentType = $httpResponseContentType;
$this->curlStatus = $curlStatus;
$this->curlErrorMessage = $curlErrorMessage;
$this->responseHeader = $responseHeader;
$this->responseBody = $responseBody;
}
public function getHttpRequestHeaders()
{
return $this->httpRequestHeaders;
}
public function getHttpResponseStatusCode()
{
return $this->httpResponseStatusCode;
}
public function getHttpResponseStatusMessage()
{
return $this->httpResponseStatusMessage;
}
public function getHttpResponseContentType()
{
return $this->httpResponseContentType;
}
public function getCurlStatus()
{
return $this->curlStatus;
}
public function curlStatusSuccess()
{
return $this->curlStatus === Curl::CURL_SUCCESS;
}
public function curlStatusFailed()
{
return $this->curlStatus === Curl::CURL_FAILED;
}
public function hasCurlErrorMessage()
{
return $this->curlErrorMessage !== null;
}
public function getCurlErrorMessage()
{
return $this->curlErrorMessage;
}
public function hasResponseHeader()
{
return $this->responseHeader !== null;
}
public function getResponseHeader()
{
return $this->responseHeader;
}
public function hasResponseBody()
{
return $this->responseBody !== null;
}
public function getResponseBody()
{
return $this->responseBody;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace BeSimple\SoapClient\Curl\Http;
class HttpAuthenticationBasicOptions implements HttpAuthenticationInterface
{
private $username;
private $password;
/**
* @param string $username
* @param string $password
*/
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function getUsername()
{
return $this->username;
}
public function getPassword()
{
return $this->password;
}
public function getAuthenticationType()
{
return HttpAuthenticationInterface::AUTHENTICATION_TYPE_BASIC;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace BeSimple\SoapClient\Curl\Http;
class HttpAuthenticationDigestOptions implements HttpAuthenticationInterface
{
public function getAuthenticationType()
{
return HttpAuthenticationInterface::AUTHENTICATION_TYPE_DIGEST;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace BeSimple\SoapClient\Curl\Http;
interface HttpAuthenticationInterface
{
const AUTHENTICATION_TYPE_ANY = CURLAUTH_ANY;
const AUTHENTICATION_TYPE_BASIC = CURLAUTH_BASIC;
const AUTHENTICATION_TYPE_DIGEST = CURLAUTH_DIGEST;
/**
* @return string choice from self::AUTHENTICATION_TYPE_ANY|self::AUTHENTICATION_TYPE_BASIC|self::AUTHENTICATION_TYPE_DIGEST
*/
public function getAuthenticationType();
}

View File

@ -0,0 +1,64 @@
<?php
namespace BeSimple\SoapClient\Curl\Http;
class SslCertificateOptions
{
private $certificateLocalPath;
private $certificatePassPhrase;
private $certificateAuthorityInfo;
private $certificateAuthorityPath;
/**
* @param string $certificateLocalPath
* @param string $certificatePassPhrase
* @param string $certificateAuthorityInfo
* @param string $certificateAuthorityPath
*/
public function __construct(
$certificateLocalPath,
$certificatePassPhrase = null,
$certificateAuthorityInfo = null,
$certificateAuthorityPath = null
) {
$this->certificateLocalPath = $certificateLocalPath;
$this->certificatePassPhrase = $certificatePassPhrase;
$this->certificateAuthorityInfo = $certificateAuthorityInfo;
$this->certificateAuthorityPath = $certificateAuthorityPath;
}
public function getCertificateLocalPath()
{
return $this->certificateLocalPath;
}
public function getCertificatePassPhrase()
{
return $this->certificatePassPhrase;
}
public function getCertificateAuthorityInfo()
{
return $this->certificateAuthorityInfo;
}
public function getCertificateAuthorityPath()
{
return $this->certificateAuthorityPath;
}
public function hasCertificatePassPhrase()
{
return $this->certificatePassPhrase !== null;
}
public function hasCertificateAuthorityInfo()
{
return $this->certificateAuthorityInfo !== null;
}
public function hasCertificateAuthorityPath()
{
return $this->certificateAuthorityPath !== null;
}
}

View File

@ -19,7 +19,7 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
use BeSimple\SoapCommon\Mime\Part; use BeSimple\SoapCommon\Mime\Part;
use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter; use BeSimple\SoapCommon\SoapRequestFilter;
use BeSimple\SoapCommon\SoapResponse; use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter; use BeSimple\SoapCommon\SoapResponseFilter;
/** /**
@ -62,7 +62,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
return $request; return $request;
} }
public function filterResponse(SoapResponse $response, $attachmentType) public function filterResponse(CommonSoapResponse $response, $attachmentType)
{ {
$multiPartMessage = MimeParser::parseMimeMessage( $multiPartMessage = MimeParser::parseMimeMessage(
$response->getContent(), $response->getContent(),

View File

@ -12,10 +12,20 @@
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
use BeSimple\SoapBundle\Soap\SoapAttachment;
use BeSimple\SoapBundle\Soap\SoapAttachmentList;
use BeSimple\SoapClient\Curl\Curl;
use BeSimple\SoapClient\Curl\CurlOptionsBuilder;
use BeSimple\SoapClient\Curl\CurlResponse;
use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapCommon\Fault\SoapFaultEnum;
use BeSimple\SoapCommon\Mime\PartFactory;
use BeSimple\SoapCommon\SoapKernel; use BeSimple\SoapCommon\SoapKernel;
use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFactory; use BeSimple\SoapCommon\SoapRequestFactory;
use Exception;
use SoapFault;
/** /**
* Extended SoapClient that uses a a cURL wrapper for all underlying HTTP * Extended SoapClient that uses a a cURL wrapper for all underlying HTTP
@ -24,73 +34,92 @@ use BeSimple\SoapCommon\SoapRequestFactory;
* allows caching of all remote referenced items. * allows caching of all remote referenced items.
* *
* @author Andreas Schamberger <mail@andreass.net> * @author Andreas Schamberger <mail@andreass.net>
* @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapClient extends \SoapClient class SoapClient extends \SoapClient
{ {
protected $soapVersion;
protected $tracingEnabled;
protected $soapClientOptions; protected $soapClientOptions;
protected $soapOptions; protected $soapOptions;
protected $curl; private $curl;
/**
* Last request headers.
*
* @var string
*/
private $lastRequestHeaders = '';
/**
* Last request.
*
* @var string
*/
private $lastRequest = '';
/**
* Last response headers.
*
* @var string
*/
private $lastResponseHeaders = '';
/**
* Last response.
*
* @var string
*/
private $lastResponse = '';
/**
* Constructor.
*
* @param SoapClientOptions $soapClientOptions
* @param SoapOptions $soapOptions
*/
public function __construct(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions) public function __construct(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions)
{ {
$this->soapVersion = $soapOptions->getSoapVersion();
$this->tracingEnabled = $soapClientOptions->getTrace();
$this->soapClientOptions = $soapClientOptions; $this->soapClientOptions = $soapClientOptions;
$this->soapOptions = $soapOptions; $this->soapOptions = $soapOptions;
$this->curl = new Curl(
// @todo: refactor SoapClient: do not use $options as array: refactor Curl CurlOptionsBuilder::buildForSoapClient($soapClientOptions)
$this->curl = new Curl($soapClientOptions->toArray());
$wsdlFile = $this->loadWsdl(
$soapOptions->getWsdlFile(),
$soapOptions->getWsdlCacheType()
); );
parent::__construct( try {
$wsdlFile, @parent::__construct(
$soapClientOptions->toArray() + $soapOptions->toArray() $this->loadWsdl(
$this->curl,
$soapOptions->getWsdlFile(),
$soapOptions->getWsdlCacheType()
),
$soapClientOptions->toArray() + $soapOptions->toArray()
);
} catch (Exception $e) {
throw new SoapFault(
SoapFaultEnum::SOAP_FAULT_SOAP_CLIENT_ERROR,
'Could not create SoapClient instance with message: '.$e->getMessage()
);
}
}
/**
* Avoid using __call directly, it's deprecated even in \SoapClient.
*
* @deprecated
*/
public function __call($function_name, $arguments)
{
throw new Exception(
'The __call method is deprecated. Use __soapCall/soapCall instead.'
); );
} }
/** /**
* Custom request method to be able to modify the SOAP messages. * Using __soapCall returns only response string, use soapCall instead.
* $oneWay parameter is not used at the moment. *
* @param string $functionName
* @param array $arguments
* @param array|null $options
* @param null $inputHeaders
* @param array|null $outputHeaders
* @return string
*/
public function __soapCall($functionName, $arguments, $options = null, $inputHeaders = null, &$outputHeaders = null)
{
return $this->soapCall($functionName, $arguments, $options, $inputHeaders, $outputHeaders)->getContent();
}
/**
* @param string $functionName
* @param array $arguments
* @param array|null $options
* @param SoapAttachment[] $soapAttachments
* @param null $inputHeaders
* @param array|null $outputHeaders
* @return SoapResponse
*/
public function soapCall($functionName, array $arguments, array $soapAttachments = [], array $options = null, $inputHeaders = null, array &$outputHeaders = null)
{
ob_start();
parent::__soapCall($functionName, $arguments, $options, $inputHeaders, $outputHeaders);
$nativeSoapClientRequest = $this->mapNativeDataJsonToDto(ob_get_clean());
return $this->performSoapRequest(
$nativeSoapClientRequest->request,
$nativeSoapClientRequest->location,
$nativeSoapClientRequest->action,
$nativeSoapClientRequest->version,
$soapAttachments
);
}
/**
* This is not performing any HTTP requests, but it is getting data from SoapClient that are needed for this Client
* *
* @param string $request Request string * @param string $request Request string
* @param string $location Location * @param string $location Location
@ -102,44 +131,105 @@ class SoapClient extends \SoapClient
*/ */
public function __doRequest($request, $location, $action, $version, $oneWay = 0) public function __doRequest($request, $location, $action, $version, $oneWay = 0)
{ {
$soapRequest = $this->createSoapRequest($location, $action, $version, $request); $soapClientNativeDataTransferObject = new SoapClientNativeDataTransferObject(
$soapResponse = $this->getSoapResponseFromRequest($soapRequest); $request,
$location,
$action,
$version
);
echo json_encode($soapClientNativeDataTransferObject);
return $soapResponse->getContent(); return $request;
}
private function createSoapRequest($location, $action, $version, $request)
{
$soapRequest = SoapRequestFactory::create($location, $action, $version, $request);
if ($this->soapOptions->hasAttachments()) {
$soapKernel = new SoapKernel();
$soapRequest = $soapKernel->filterRequest(
$soapRequest,
$this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType()
);
}
return $soapRequest;
} }
/** /**
* Runs the currently registered request filters on the request, performs * Custom request method to be able to modify the SOAP messages.
* the HTTP request and runs the response filters. * $oneWay parameter is not used at the moment.
* *
* @param SoapRequest $soapRequest SOAP request object * @param mixed $request Request object
* @param string $location Location
* @param string $action SOAP action
* @param int $version SOAP version
* @param SoapAttachment[] $soapAttachments SOAP attachments array
* *
* @return SoapResponse * @return SoapResponse
*/ */
private function getSoapResponseFromRequest(SoapRequest $soapRequest) public function performSoapRequest($request, $location, $action, $version, array $soapAttachments = [])
{ {
$soapResponse = $this->performHttpSoapRequest($soapRequest); $soapRequest = $this->createSoapRequest($location, $action, $version, $request, $soapAttachments);
if ($this->soapOptions->hasAttachments()) {
$soapKernel = new SoapKernel(); return $this->performHttpSoapRequest($soapRequest);
$soapKernel->filterResponse($soapResponse, $this->getAttachmentFilters(), $this->soapOptions->getAttachmentType()); }
/** @deprecated */
public function __getLastRequestHeaders()
{
$this->checkTracing();
throw new Exception(
'The __getLastRequestHeaders method is now deprecated. Use callSoapRequest instead and get the tracing information from SoapResponseTracingData.'
);
}
/** @deprecated */
public function __getLastRequest()
{
$this->checkTracing();
throw new Exception(
'The __getLastRequest method is now deprecated. Use callSoapRequest instead and get the tracing information from SoapResponseTracingData.'
);
}
/** @deprecated */
public function __getLastResponseHeaders()
{
$this->checkTracing();
throw new Exception(
'The __getLastResponseHeaders method is now deprecated. Use callSoapRequest instead and get the tracing information from SoapResponseTracingData.'
);
}
/** @deprecated */
public function __getLastResponse()
{
$this->checkTracing();
throw new Exception(
'The __getLastResponse method is now deprecated. Use callSoapRequest instead and get the tracing information from SoapResponseTracingData.'
);
}
/**
* @param string $location Location
* @param string $action SOAP action
* @param int $version SOAP version
* @param string $request SOAP request body
* @param SoapAttachment[] $soapAttachments array of SOAP attachments
*
* @return SoapRequest
*/
private function createSoapRequest($location, $action, $version, $request, array $soapAttachments = [])
{
$soapAttachmentList = new SoapAttachmentList($soapAttachments);
$soapRequest = SoapRequestFactory::create($location, $action, $version, $request);
if (count($soapAttachments) > 0) {
if ($this->soapOptions->hasAttachments() === true) {
$soapRequest->setAttachments(PartFactory::createAttachmentParts($soapAttachments));
$soapRequest = SoapKernel::filterRequest(
$soapRequest,
$this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType()
);
} else {
throw new Exception(
'Non SWA SoapClient cannot handle SOAP action '.$action.' with attachments: '.implode(', ', $soapAttachmentList->getSoapAttachmentIds())
);
}
} }
return $soapResponse; return $soapRequest;
} }
/** /**
@ -147,147 +237,92 @@ class SoapClient extends \SoapClient
* *
* @param SoapRequest $soapRequest SoapRequest object * @param SoapRequest $soapRequest SoapRequest object
* @return SoapResponse * @return SoapResponse
* @throws SoapFault
*/ */
private function performHttpSoapRequest(SoapRequest $soapRequest) private function performHttpSoapRequest(SoapRequest $soapRequest)
{ {
// HTTP headers if ($soapRequest->getVersion() === SOAP_1_1) {
$soapVersion = $soapRequest->getVersion();
$soapAction = $soapRequest->getAction();
if (SOAP_1_1 === $soapVersion) {
$headers = [ $headers = [
'Content-Type:' . $soapRequest->getContentType(), 'Content-Type:' . $soapRequest->getContentType(),
'SOAPAction: "' . $soapAction . '"', 'SOAPAction: "' . $soapRequest->getAction() . '"',
]; ];
} else { } else {
$headers = [ $headers = [
'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapAction . '"', 'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapRequest->getAction() . '"',
]; ];
} }
$curlResponse = $this->curl->executeCurlWithCachedSession(
$location = $soapRequest->getLocation();
$content = $soapRequest->getContent();
$headers = $this->filterRequestHeaders($soapRequest, $headers);
$options = $this->filterRequestOptions($soapRequest);
$responseSuccessful = $this->curl->exec(
$location,
$content,
$headers,
$options
);
// tracing enabled: store last request header and body
if ($this->tracingEnabled === true) {
$this->lastRequestHeaders = $this->curl->getRequestHeaders();
$this->lastRequest = $soapRequest->getContent();
}
// in case of an error while making the http request throw a soapFault
if ($responseSuccessful === false) {
// get error message from curl
$faultstring = $this->curl->getErrorMessage();
throw new \SoapFault('HTTP', $faultstring);
}
// tracing enabled: store last response header and body
if ($this->tracingEnabled === true) {
$this->lastResponseHeaders = $this->curl->getResponseHeaders();
$this->lastResponse = $this->curl->getResponseBody();
}
// wrap response data in SoapResponse object
$soapResponse = SoapResponse::create(
$this->curl->getResponseBody(),
$soapRequest->getLocation(), $soapRequest->getLocation(),
$soapRequest->getAction(), $soapRequest->getContent(),
$soapRequest->getVersion(), $headers
$this->curl->getResponseContentType() );
$soapResponseTracingData = new SoapResponseTracingData(
$curlResponse->getHttpRequestHeaders(),
$soapRequest->getContent(),
$curlResponse->getResponseHeader(),
$curlResponse->getResponseBody()
); );
return $soapResponse; if ($curlResponse->curlStatusSuccess()) {
$soapResponse = $this->returnSoapResponseByTracing(
$this->soapClientOptions->getTrace(),
$soapRequest,
$curlResponse,
$soapResponseTracingData
);
if ($this->soapOptions->hasAttachments()) {
return SoapKernel::filterResponse(
$soapResponse,
$this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType()
);
} else {
return $soapResponse;
}
} else if ($curlResponse->curlStatusFailed()) {
return $this->throwSoapFaultByTracing(
$this->soapClientOptions->getTrace(),
SoapFaultEnum::SOAP_FAULT_HTTP.'-'.$curlResponse->getHttpResponseStatusCode(),
$curlResponse->getCurlErrorMessage(),
$soapResponseTracingData
);
} else {
return $this->throwSoapFaultByTracing(
$this->soapClientOptions->getTrace(),
SoapFaultEnum::SOAP_FAULT_SOAP_CLIENT_ERROR,
'Cannot process curl response with unresolved status: ' . $curlResponse->getCurlStatus(),
$soapResponseTracingData
);
}
} }
/** /**
* Filters HTTP headers which will be sent * @param Curl $curl
* * @param string $wsdlPath
* @param SoapRequest $soapRequest SOAP request object * @param int $wsdlCacheType
* @param array $headers An array of HTTP headers
*
* @return array
*/
protected function filterRequestHeaders(SoapRequest $soapRequest, array $headers)
{
return $headers;
}
/**
* Adds additional cURL options for the request
*
* @param SoapRequest $soapRequest SOAP request object
*
* @return array
*/
protected function filterRequestOptions(SoapRequest $soapRequest)
{
return [];
}
/**
* Get last request HTTP headers.
*
* @return string
*/
public function __getLastRequestHeaders()
{
return $this->lastRequestHeaders;
}
/**
* Get last request HTTP body.
*
* @return string
*/
public function __getLastRequest()
{
return $this->lastRequest;
}
/**
* Get last response HTTP headers.
*
* @return string
*/
public function __getLastResponseHeaders()
{
return $this->lastResponseHeaders;
}
/**
* Get last response HTTP body.
*
* @return string
*/
public function __getLastResponse()
{
return $this->lastResponse;
}
/**
* @param string $wsdl
* @param int $wsdlCache
* @param bool $resolveRemoteIncludes * @param bool $resolveRemoteIncludes
* *
* @return string * @return string
*/ */
private function loadWsdl($wsdl, $wsdlCache, $resolveRemoteIncludes = true) private function loadWsdl(Curl $curl, $wsdlPath, $wsdlCacheType, $resolveRemoteIncludes = true)
{ {
$wsdlDownloader = new WsdlDownloader($this->curl, $resolveRemoteIncludes, $wsdlCache);
$wsdlDownloader = new WsdlDownloader($curl);
try { try {
$cacheFileName = $wsdlDownloader->download($wsdl); $loadedWsdlFilePath = $wsdlDownloader->getWsdlPath($curl, $wsdlPath, $wsdlCacheType, $resolveRemoteIncludes);
} catch (\RuntimeException $e) { } catch (Exception $e) {
throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "'"); throw new SoapFault(
SoapFaultEnum::SOAP_FAULT_WSDL,
'Unable to load WsdlPath: ' . $e->getMessage()
);
} }
return $cacheFileName; return $loadedWsdlFilePath;
} }
private function getAttachmentFilters() private function getAttachmentFilters()
@ -302,4 +337,74 @@ class SoapClient extends \SoapClient
return $filters; return $filters;
} }
private function mapNativeDataJsonToDto($nativeDataJson)
{
$nativeData = json_decode($nativeDataJson);
return new SoapClientNativeDataTransferObject(
$nativeData->request,
$nativeData->location,
$nativeData->action,
$nativeData->version
);
}
private function returnSoapResponseByTracing(
$isTracingEnabled,
SoapRequest $soapRequest,
CurlResponse $curlResponse,
SoapResponseTracingData $soapResponseTracingData,
array $soapAttachments = []
) {
if ($isTracingEnabled === true) {
return SoapResponseFactory::createWithTracingData(
$curlResponse->getResponseBody(),
$soapRequest->getLocation(),
$soapRequest->getAction(),
$soapRequest->getVersion(),
$curlResponse->getHttpResponseContentType(),
$soapResponseTracingData,
$soapAttachments
);
} else {
return SoapResponseFactory::create(
$curlResponse->getResponseBody(),
$soapRequest->getLocation(),
$soapRequest->getAction(),
$soapRequest->getVersion(),
$curlResponse->getHttpResponseContentType(),
$soapAttachments
);
}
}
private function throwSoapFaultByTracing($isTracingEnabled, $soapFaultCode, $soapFaultMessage, SoapResponseTracingData $soapResponseTracingData)
{
if ($isTracingEnabled === true) {
throw new SoapFaultWithTracingData(
$soapFaultCode,
$soapFaultMessage,
$soapResponseTracingData
);
} else {
throw new SoapFault(
$soapFaultCode,
$soapFaultMessage
);
}
}
private function checkTracing()
{
if ($this->soapClientOptions->getTrace() === false) {
throw new Exception('SoapClientOptions tracing disabled, turn on trace attribute');
}
}
} }

View File

@ -12,22 +12,44 @@
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
use BeSimple\SoapBundle\Cache;
use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use Exception;
use SoapHeader;
/** /**
* Provides a SoapClient instance. * Provides a SoapClient instance.
* *
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapClientBuilder class SoapClientBuilder
{ {
public function build(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions) public function build(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions)
{ {
$cache = new Cache($soapOptions);
$cache->validateSettings($soapOptions);
return new SoapClient( return new SoapClient(
$soapClientOptions, $soapClientOptions,
$soapOptions $soapOptions
); );
} }
public function buildWithSoapHeader(
SoapClientOptions $soapClientOptions,
SoapOptions $soapOptions,
SoapHeader $soapHeader
) {
$soapClient = $this->build($soapClientOptions, $soapOptions);
if ($soapClient->__setSoapHeaders($soapHeader) === false) {
throw new Exception(
'Could not set SoapHeader: '.var_export($soapHeader, true)
);
}
return $soapClient;
}
} }

View File

@ -0,0 +1,7 @@
<?php
namespace BeSimple\SoapClient;
class SoapClientMessageWithAttachments
{
}

View File

@ -0,0 +1,19 @@
<?php
namespace BeSimple\SoapClient;
class SoapClientNativeDataTransferObject
{
public $request;
public $location;
public $action;
public $version;
public function __construct($request, $location, $action, $version)
{
$this->request = $request;
$this->location = $location;
$this->action = $action;
$this->version = $version;
}
}

View File

@ -12,12 +12,16 @@
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
use BeSimple\SoapClient\Curl\CurlOptions;
use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationInterface;
/** /**
* Provides a SoapClient instance. * Provides a SoapClient instance.
* *
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapClientOptionsBuilder class SoapClientOptionsBuilder
{ {
@ -26,8 +30,29 @@ class SoapClientOptionsBuilder
return new SoapClientOptions( return new SoapClientOptions(
SoapClientOptions::SOAP_CLIENT_TRACE_OFF, SoapClientOptions::SOAP_CLIENT_TRACE_OFF,
SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON, SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON,
'BeSimpleSoap', CurlOptions::DEFAULT_USER_AGENT,
SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE
); );
} }
public static function createWithTracing()
{
return new SoapClientOptions(
SoapClientOptions::SOAP_CLIENT_TRACE_ON,
SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON,
CurlOptions::DEFAULT_USER_AGENT,
SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE
);
}
public static function createWithAuthentication(SoapServerAuthenticationInterface $authentication)
{
return new SoapClientOptions(
SoapClientOptions::SOAP_CLIENT_TRACE_ON,
SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON,
CurlOptions::DEFAULT_USER_AGENT,
SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE,
$authentication
);
}
} }

View File

@ -0,0 +1,21 @@
<?php
namespace BeSimple\SoapClient;
use SoapFault;
class SoapFaultWithTracingData extends SoapFault
{
private $soapResponseTracingData;
public function __construct($code = 0, $message = "", SoapResponseTracingData $soapResponseTracingData)
{
$this->soapResponseTracingData = $soapResponseTracingData;
parent::__construct($code, $message);
}
public function getSoapResponseTracingData()
{
return $this->soapResponseTracingData;
}
}

View File

@ -1,7 +1,10 @@
<?php <?php
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient\SoapOptions;
use BeSimple\SoapClient\Curl\CurlOptions;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationBasic;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationDigest;
use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationInterface; use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationInterface;
use BeSimple\SoapClient\SoapServerProxy\SoapServerProxy; use BeSimple\SoapClient\SoapServerProxy\SoapServerProxy;
@ -11,9 +14,9 @@ class SoapClientOptions
const SOAP_CLIENT_TRACE_OFF = false; const SOAP_CLIENT_TRACE_OFF = false;
const SOAP_CLIENT_EXCEPTIONS_ON = true; const SOAP_CLIENT_EXCEPTIONS_ON = true;
const SOAP_CLIENT_EXCEPTIONS_OFF = false; const SOAP_CLIENT_EXCEPTIONS_OFF = false;
const SOAP_CLIENT_COMPRESSION_NONE = null; const SOAP_CLIENT_COMPRESSION_NONE = CurlOptions::SOAP_COMPRESSION_NONE;
const SOAP_CLIENT_COMPRESSION_GZIP = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP; const SOAP_CLIENT_COMPRESSION_GZIP = CurlOptions::SOAP_COMPRESSION_GZIP;
const SOAP_CLIENT_COMPRESSION_DEFLATE = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_DEFLATE; const SOAP_CLIENT_COMPRESSION_DEFLATE = CurlOptions::SOAP_COMPRESSION_DEFLATE;
private $trace; private $trace;
private $exceptions; private $exceptions;
@ -70,6 +73,16 @@ class SoapClientOptions
return $this->authentication !== null; return $this->authentication !== null;
} }
public function hasAuthenticationBasic()
{
return $this->hasAuthentication() && $this->getAuthentication() instanceof SoapServerAuthenticationBasic;
}
public function hasAuthenticationDigest()
{
return $this->hasAuthentication() && $this->getAuthentication() instanceof SoapServerAuthenticationDigest;
}
public function hasProxy() public function hasProxy()
{ {
return $this->proxy !== null; return $this->proxy !== null;

View File

@ -1,46 +1,13 @@
<?php <?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
/**
* SoapResponse class for SoapClient. Provides factory function for response object.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class SoapResponse extends CommonSoapResponse class SoapResponse extends CommonSoapResponse
{ {
/** public function getResponseContent()
* Factory function for SoapResponse.
*
* @param string $content Content
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
* @param string $contentType Content type header
*
* @return SoapResponse
*/
public static function create($content, $location, $action, $version, $contentType)
{ {
$response = new SoapResponse(); return $this->getContent();
$response->setContent($content);
$response->setLocation($location);
$response->setAction($action);
$response->setVersion($version);
$response->setContentType($contentType);
return $response;
} }
} }

View File

@ -0,0 +1,98 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapClient;
use BeSimple\SoapBundle\Soap\SoapAttachment;
use BeSimple\SoapCommon\Mime\PartFactory;
/**
* SoapResponseFactory for SoapClient. Provides factory function for SoapResponse object.
*
* @author Andreas Schamberger <mail@andreass.net>
* @author Petr Bechyně <mail@petrbechyne.com>
*/
class SoapResponseFactory
{
/**
* Factory method for SoapClient\SoapResponse.
*
* @param string $content Content
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
* @param string $contentType Content type header
* @param SoapAttachment[] $attachments SOAP attachments
* @return SoapResponse
*/
public static function create(
$content,
$location,
$action,
$version,
$contentType,
array $attachments = []
) {
$response = new SoapResponse();
$response->setContent($content);
$response->setLocation($location);
$response->setAction($action);
$response->setVersion($version);
$response->setContentType($contentType);
if (count($attachments) > 0) {
$response->setAttachments(
self::createAttachmentParts($attachments)
);
}
return $response;
}
/**
* Factory method for SoapClient\SoapResponse with SoapResponseTracingData.
*
* @param string $content Content
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
* @param string $contentType Content type header
* @param SoapResponseTracingData $tracingData Data value object suitable for tracing SOAP traffic
* @param SoapAttachment[] $attachments SOAP attachments
* @return SoapResponse
*/
public static function createWithTracingData(
$content,
$location,
$action,
$version,
$contentType,
SoapResponseTracingData $tracingData,
array $attachments = []
) {
$response = new SoapResponse();
$response->setContent($content);
$response->setLocation($location);
$response->setAction($action);
$response->setVersion($version);
$response->setContentType($contentType);
if ($tracingData !== null) {
$response->setTracingData($tracingData);
}
if (count($attachments) > 0) {
$response->setAttachments(
PartFactory::createAttachmentParts($attachments)
);
}
return $response;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace BeSimple\SoapClient;
class SoapResponseTracingData
{
private $lastRequestHeaders;
private $lastRequest;
private $lastResponseHeaders;
private $lastResponse;
public function __construct($lastRequestHeaders, $lastRequest, $lastResponseHeaders, $lastResponse)
{
$this->lastRequestHeaders = $lastRequestHeaders;
$this->lastRequest = $lastRequest;
$this->lastResponseHeaders = $lastResponseHeaders;
$this->lastResponse = $lastResponse;
}
public function getLastRequestHeaders()
{
return $this->lastRequestHeaders;
}
public function getLastRequest()
{
return $this->lastRequest;
}
public function getLastResponseHeaders()
{
return $this->lastResponseHeaders;
}
public function getLastResponse()
{
return $this->lastResponse;
}
}

View File

@ -12,7 +12,8 @@
namespace BeSimple\SoapClient\Tests; namespace BeSimple\SoapClient\Tests;
use BeSimple\SoapClient\Curl; use BeSimple\SoapClient\Curl\Curl;
use BeSimple\SoapClient\Curl\CurlOptionsBuilder;
/** /**
* @author Andreas Schamberger <mail@andreass.net> * @author Andreas Schamberger <mail@andreass.net>
@ -21,11 +22,12 @@ class CurlTest extends AbstractWebserverTest
{ {
public function testExec() public function testExec()
{ {
$curl = new Curl(array( $curlOptions = CurlOptionsBuilder::buildDefault();
'proxy_host' => false, $curl = new Curl(
)); $curlOptions
);
$this->assertTrue($curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT))); $this->assertTrue($curl->executeCurl($curlOptions, sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT)));
$this->assertTrue($curl->exec(sprintf('http://localhost:%d/404.txt', WEBSERVER_PORT))); $this->assertTrue($curl->exec(sprintf('http://localhost:%d/404.txt', WEBSERVER_PORT)));
} }
@ -81,43 +83,4 @@ class CurlTest extends AbstractWebserverTest
$curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT)); $curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT));
$this->assertEquals('This is a testfile for cURL.', $curl->getResponseBody()); $this->assertEquals('This is a testfile for cURL.', $curl->getResponseBody());
} }
public function testGetResponseContentType()
{
$curl = new Curl(array(
'proxy_host' => false,
));
$curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT));
$this->assertEquals('text/plain; charset=UTF-8', $curl->getResponseContentType());
$curl->exec(sprintf('http://localhost:%d/404.txt', WEBSERVER_PORT));
$this->assertEquals('text/html; charset=UTF-8', $curl->getResponseContentType());
}
public function testGetResponseHeaders()
{
$curl = new Curl(array(
'proxy_host' => false,
));
$curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT));
$this->assertEquals(117 + self::$websererPortLength, strlen($curl->getResponseHeaders()));
$curl->exec(sprintf('http://localhost:%d/404.txt', WEBSERVER_PORT));
$this->assertEquals(124 + self::$websererPortLength, strlen($curl->getResponseHeaders()));
}
public function testGetResponseStatusCode()
{
$curl = new Curl(array(
'proxy_host' => false,
));
$curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT));
$this->assertEquals(200, $curl->getResponseStatusCode());
$curl->exec(sprintf('http://localhost:%d/404.txt', WEBSERVER_PORT));
$this->assertEquals(404, $curl->getResponseStatusCode());
}
} }

View File

@ -0,0 +1,8 @@
<?php
namespace BeSimple\SoapClient\Tests;
class GenerateTestRequest
{
public $salutation;
}

View File

@ -0,0 +1,8 @@
<?php
namespace BeSimple\SoapClient\Tests;
class GetUKLocationByCounty
{
public $County;
}

View File

@ -0,0 +1,23 @@
# HOW TO PREPARE Mock SWA Service
1. Create an test endpoint by using tools such as mockable.io, that will return response (expect POST requests with SOAP v1.1)
similar to the one provided in example file src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response
and Content-Type headers from src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response.headers
Example Endpoint URL: https://demo0580999.mockable.io/soap/testGenerator
2. Create a test WSDL endpoint that will return WSDL file from (expect GET requests)
an example file src/BeSimple/SoapClient/Tests/Mock/MockSwaService.wsdl
Example WSDL endpoint URL: https://demo0580999.mockable.io/soap/testGenerator?WSDL
3. Take the two endpoints and configure SoapClientBuilderTest::TEST_ENDPOINT_SWA
and SoapClientBuilderTest::TEST_REMOTE_WSDL_SWA variables
4. The test BeSimple\SoapClient\Tests\SoapClientBuilderTest::testSoapCallSwaWithAttachmentsOnResponse should work correctly.
If not, try to catch SoapFaultWithTracingData thrown by the soapCall method:
$soapClient->soapCall('generateTest', [$generateTestRequest]);
You can print the SoapFaultWithTracingData attributes in order to investigate the SoapClient request and request headers.

View File

@ -0,0 +1,18 @@
--Part_13_58a1b01a466a6.58a1b01a466e8
Content-Type: application/soap+xml; charset=utf-8
Content-Transfer-Encoding: 8bit
Content-ID: <part-72f5d209-2631-42ee-81d1-e554af739ce6@response.info>
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://schema.testcase"><env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"><ns1:generateTestResponse><rpc:result>generateTestReturn</rpc:result><generateTestReturn><fileName>dummy-attachment.txt</fileName></generateTestReturn></ns1:generateTestResponse></env:Body></env:Envelope>
--Part_13_58a1b01a466a6.58a1b01a466e8
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Content-ID: <dummy-attachment.txt>
Content-Location: dummy-attachment.txt
Hello world!
--Part_13_58a1b01a466a6.58a1b01a466e8--

View File

@ -0,0 +1 @@
multipart/related; type="application/soap+xml"; charset=utf-8; boundary=Part_13_58a1b01a466a6.58a1b01a466e8; start="<part-72f5d209-2631-42ee-81d1-e554af739ce6@response.info>"

View File

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://schema.testcase" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://schema.testcase">
<wsdl:types>
<xsd:schema targetNamespace="http://schema.testcase">
<xsd:complexType name="SoapHeaderEntity">
<xsd:sequence>
<xsd:element name="user" type="xsd:string" minOccurs="1">
<xsd:annotation>
<xsd:documentation>User name for authorization</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="SoapHeader" type="tns:SoapHeaderEntity"/>
<xsd:complexType name="GenerateTestRequest">
<xsd:sequence>
<xsd:element name="salutation" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="GenerateTestResponse">
<xsd:sequence>
<xsd:element name="fileName" type="xsd:string" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<message name="SoapHeader">
<part name="SoapHeader" element="tns:SoapHeader"/>
</message>
<message name="GenerateTestRequest">
<part name="request" type="tns:GenerateTestRequest"/>
</message>
<message name="GenerateTestResponse">
<part name="generateTestReturn" type="tns:GenerateTestResponse"/>
</message>
<wsdl:portType name="TestGeneratorServiceSoapPortType">
<wsdl:operation name="generateTest">
<wsdl:input message="tns:GenerateTestRequest"/>
<wsdl:output message="tns:GenerateTestResponse"/>
</wsdl:operation>
</wsdl:portType>
<binding name="TestGeneratorServiceSoapBinding" type="tns:TestGeneratorServiceSoapPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="generateTest">
<soap:operation soapAction="TestGeneratorService.generateTest" style="rpc"/>
<wsdl:input>
<soap:header use="literal" message="tns:SoapHeader" part="SoapHeader" namespace="http://schema.testcase"/>
<soap:body use="literal" part="request" namespace="http://schema.testcase"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal" namespace="http://schema.testcase"/>
</wsdl:output>
</wsdl:operation>
</binding>
<wsdl:service name="TestGeneratorService">
<xsd:documentation>WSDL file for TestGeneratorService</xsd:documentation>
<port name="TestGeneratorServiceSoapPortType" binding="tns:TestGeneratorServiceSoapBinding">
<soap:address location="https://demo0580999.mockable.io/soap/testGenerator"/>
</port>
</wsdl:service>
</wsdl:definitions>

View File

@ -12,113 +12,286 @@
namespace BeSimple\SoapClient\Tests; namespace BeSimple\SoapClient\Tests;
use BeSimple\SoapBundle\Soap\SoapAttachment;
use BeSimple\SoapClient\Curl\CurlOptions;
use BeSimple\SoapClient\SoapClientBuilder; use BeSimple\SoapClient\SoapClientBuilder;
use BeSimple\SoapClient\SoapClientOptionsBuilder;
use BeSimple\SoapClient\SoapFaultWithTracingData;
use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapCommon\ClassMap;
use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use BeSimple\SoapCommon\SoapOptionsBuilder;
use Exception;
use SoapHeader;
class SoapClientBuilderTest extends \PHPUnit_Framework_TestCase class SoapClientBuilderTest extends \PHPUnit_Framework_TestCase
{ {
private $defaultOptions = array( const CACHE_DIR = __DIR__ . '/../../../../cache';
'features' => 0, const TEST_ENDPOINT_UK = 'http://www.webservicex.net/uklocation.asmx';
'classmap' => array(), const TEST_REMOTE_WSDL_UK = 'http://www.webservicex.net/uklocation.asmx?WSDL';
'typemap' => array(), const TEST_LOCAL_WSDL_UK = __DIR__.'/localWsdl.wsdl';
); const TEST_REMOTE_WSDL_NOT_WORKING = 'http://www.nosuchserverexist.tld/doesnotexist.endpoint?wsdl';
const TEST_ENDPOINT_SWA = 'https://demo2815480.mockable.io/soap/testGenerator';
const TEST_REMOTE_WSDL_SWA = 'https://demo2815480.mockable.io/soap/testGenerator?WSDL';
public function testContruct() public function testSoapOptionsCreateWithDefaults()
{ {
$options = $this $defaultOptions = SoapOptionsBuilder::createWithDefaults(self::TEST_LOCAL_WSDL_UK);
->getSoapBuilder()
->getSoapOptions()
;
$this->assertEquals($this->mergeOptions(array()), $options); self::assertInstanceOf(SoapOptions::class, $defaultOptions);
self::assertEquals(self::TEST_LOCAL_WSDL_UK, $defaultOptions->getWsdlFile());
} }
public function testWithTrace() public function testSoapClientOptionsCreateWithDefaults()
{ {
$builder = $this->getSoapBuilder(); $defaultOptions = SoapClientOptionsBuilder::createWithDefaults();
$builder->withTrace(); self::assertInstanceOf(SoapClientOptions::class, $defaultOptions);
$this->assertEquals($this->mergeOptions(array('trace' => true)), $builder->getSoapOptions()); self::assertEquals(CurlOptions::DEFAULT_USER_AGENT, $defaultOptions->getUserAgent());
$builder->withTrace(false);
$this->assertEquals($this->mergeOptions(array('trace' => false)), $builder->getSoapOptions());
} }
public function testWithExceptions() public function testConstructSoapClientWithDefaults()
{ {
$builder = $this->getSoapBuilder(); $soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(self::TEST_REMOTE_WSDL_UK)
);
$builder->withExceptions(); self::assertInstanceOf(\SoapClient::class, $soapClient);
$this->assertEquals($this->mergeOptions(array('exceptions' => true)), $builder->getSoapOptions());
$builder->withExceptions(false);
$this->assertEquals($this->mergeOptions(array('exceptions' => false)), $builder->getSoapOptions());
} }
public function testWithUserAgent() public function testConstructSoapClientWithSwaAndClassMapAndCacheDisk()
{ {
$builder = $this->getSoapBuilder(); $soapOptions = SoapOptionsBuilder::createSwaWithClassMap(
self::TEST_REMOTE_WSDL_UK,
new ClassMap(),
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__.'/../../../../cache'
);
$builder->withUserAgent('BeSimpleSoap Test'); $soapClient = $this->getSoapBuilder()->build(
$this->assertEquals($this->mergeOptions(array('user_agent' => 'BeSimpleSoap Test')), $builder->getSoapOptions()); SoapClientOptionsBuilder::createWithDefaults(),
$soapOptions
);
self::assertInstanceOf(\SoapClient::class, $soapClient);
} }
public function testWithCompression() public function testConstructSoapClientWithDefaultsAndLocalWsdlFile()
{ {
$builder = $this->getSoapBuilder(); $soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(self::TEST_LOCAL_WSDL_UK)
);
$builder->withCompressionGzip(); self::assertInstanceOf(\SoapClient::class, $soapClient);
$this->assertEquals($this->mergeOptions(array('compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP)), $builder->getSoapOptions());
$builder->withCompressionDeflate();
$this->assertEquals($this->mergeOptions(array('compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_DEFLATE)), $builder->getSoapOptions());
} }
public function testWithAuthentication() public function testConstructSoapClientWithSwaAndClassMapAndCacheDiskAndLocalWsdlFile()
{ {
$builder = $this->getSoapBuilder(); $soapOptions = SoapOptionsBuilder::createSwaWithClassMap(
self::TEST_LOCAL_WSDL_UK,
new ClassMap(),
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__ .'/../../../../cache'
);
$builder->withDigestAuthentication(__DIR__.'/Fixtures/cert.pem', 'foobar'); $soapClient = $this->getSoapBuilder()->build(
$this->assertEquals($this->mergeOptions(array('authentication' => SOAP_AUTHENTICATION_DIGEST, 'local_cert' => __DIR__.'/Fixtures/cert.pem', 'passphrase' => 'foobar')), $builder->getSoapOptions()); SoapClientOptionsBuilder::createWithDefaults(),
$soapOptions
);
$builder->withDigestAuthentication(__DIR__.'/Fixtures/cert.pem'); self::assertInstanceOf(\SoapClient::class, $soapClient);
$this->assertEquals($this->mergeOptions(array('authentication' => SOAP_AUTHENTICATION_DIGEST, 'local_cert' => __DIR__.'/Fixtures/cert.pem')), $builder->getSoapOptions());
$builder->withBasicAuthentication('foo', 'bar');
$this->assertEquals($this->mergeOptions(array('authentication' => SOAP_AUTHENTICATION_BASIC, 'login' => 'foo', 'password' => 'bar')), $builder->getSoapOptions());
} }
public function testWithProxy() public function testSoapCall()
{ {
$builder = $this->getSoapBuilder(); $soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(self::TEST_REMOTE_WSDL_UK)
);
$getUKLocationByCountyRequest = new GetUKLocationByCounty();
$getUKLocationByCountyRequest->County = 'London';
$soapResponse = $soapClient->soapCall('GetUKLocationByCounty', [$getUKLocationByCountyRequest]);
$builder->withProxy('localhost', 8080); self::assertContains('GetUKLocationByCountyResult', $soapResponse->getContent());
$this->assertEquals($this->mergeOptions(array('proxy_host' => 'localhost', 'proxy_port' => 8080)), $builder->getSoapOptions()); self::assertContains('</GetUKLocationByCountyResponse>', $soapResponse->getContent());
self::assertEquals(self::TEST_ENDPOINT_UK, $soapResponse->getLocation());
}
$builder->withProxy('127.0.0.1', 8585, 'foo', 'bar'); public function testSoapCallWithCacheEndpointDownShouldFail()
$this->assertEquals($this->mergeOptions(array('proxy_host' => '127.0.0.1', 'proxy_port' => 8585, 'proxy_login' => 'foo', 'proxy_password' => 'bar')), $builder->getSoapOptions()); {
$this->setExpectedException(Exception::class, 'Could not write WSDL cache file: Download failed with message');
$builder->withProxy('127.0.0.1', 8585, 'foo', 'bar', \CURLAUTH_BASIC); $this->getSoapBuilder()->build(
$this->assertEquals($this->mergeOptions(array('proxy_host' => '127.0.0.1', 'proxy_port' => 8585, 'proxy_login' => 'foo', 'proxy_password' => 'bar', 'proxy_auth' => \CURLAUTH_BASIC)), $builder->getSoapOptions()); SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(
self::TEST_REMOTE_WSDL_NOT_WORKING,
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__ .'/../../../../cache'
)
);
}
$builder->withProxy('127.0.0.1', 8585, 'foo', 'bar', \CURLAUTH_NTLM); public function testSoapCallEndpointDownShouldFail()
$this->assertEquals($this->mergeOptions(array('proxy_host' => '127.0.0.1', 'proxy_port' => 8585, 'proxy_login' => 'foo', 'proxy_password' => 'bar', 'proxy_auth' => \CURLAUTH_NTLM)), $builder->getSoapOptions()); {
$this->setExpectedException(Exception::class, 'Parsing WSDL: Couldn\'t load from');
$this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(self::TEST_REMOTE_WSDL_NOT_WORKING)
);
}
public function testSoapCallNoSwaWithAttachmentMustFail()
{
$this->setExpectedException(Exception::class, 'Non SWA SoapClient cannot handle SOAP action');
$soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithDefaults(),
SoapOptionsBuilder::createWithDefaults(self::TEST_REMOTE_WSDL_UK)
);
$getUKLocationByCountyRequest = new GetUKLocationByCounty();
$getUKLocationByCountyRequest->County = 'London';
$soapClient->soapCall(
'GetUKLocationByCounty',
[$getUKLocationByCountyRequest],
[
new SoapAttachment(
'first-file.txt',
'text/plain',
'unexpected file - no SWA - must fail'
),
]
);
}
public function testSoapCallSwaWithTwoAttachments()
{
$soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithTracing(),
SoapOptionsBuilder::createSwaWithClassMap(
self::TEST_REMOTE_WSDL_UK,
new ClassMap(),
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__ .'/../../../../cache'
)
);
$getUKLocationByCountyRequest = new GetUKLocationByCounty();
$getUKLocationByCountyRequest->County = 'London';
try { try {
$builder->withProxy('127.0.0.1', 8585, 'foo', 'bar', -100); $soapResponse = $soapClient->soapCall(
'GetUKLocationByCounty',
$this->fail('An expected exception has not been raised.'); [$getUKLocationByCountyRequest],
} catch (\Exception $e) { [
$this->assertInstanceOf('InvalidArgumentException', $e); new SoapAttachment(
'first-file.txt',
'text/plain',
'hello world'
),
new SoapAttachment(
'second-file.txt',
'text/plain',
'hello world'
)
]
);
$tracingData = $soapResponse->getTracingData();
} catch (SoapFaultWithTracingData $e) {
$tracingData = $e->getSoapResponseTracingData();
} }
self::assertEquals(
$this->getContentId($tracingData->getLastRequestHeaders()),
$this->getContentId($tracingData->getLastRequest()),
'Content ID must match in request XML and Content-Type: ...; start header'
);
self::assertEquals(
$this->getMultiPartBoundary($tracingData->getLastRequestHeaders()),
$this->getMultiPartBoundary($tracingData->getLastRequest()),
'MultiPart boundary must match in request XML and Content-Type: ...; boundary header'
);
self::assertContains('boundary=Part_', $tracingData->getLastRequestHeaders(), 'Headers should link to boundary');
self::assertContains('start="<part-', $tracingData->getLastRequestHeaders(), 'Headers should link to first MultiPart');
self::assertContains('action="', $tracingData->getLastRequestHeaders(), 'Headers should contain SOAP action');
self::assertEquals(
$this->removeOneTimeData(file_get_contents(__DIR__.'/soapRequestWithTwoAttachments.request')),
$this->removeOneTimeData($tracingData->getLastRequest()),
'Requests must match after onetime data were removed'
);
} }
public function testCreateWithDefaults() public function testSoapCallSwaWithNoAttachments()
{ {
$builder = SoapClientBuilder::createClientWithDefaults(); $soapClient = $this->getSoapBuilder()->build(
SoapClientOptionsBuilder::createWithTracing(),
SoapOptionsBuilder::createSwaWithClassMap(
self::TEST_REMOTE_WSDL_UK,
new ClassMap(),
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__ .'/../../../../cache'
)
);
$getUKLocationByCountyRequest = new GetUKLocationByCounty();
$getUKLocationByCountyRequest->County = 'London';
$this->assertInstanceOf('BeSimple\SoapClient\SoapClientBuilder', $builder); try {
$soapResponse = $soapClient->soapCall(
'GetUKLocationByCounty',
[$getUKLocationByCountyRequest]
);
$tracingData = $soapResponse->getTracingData();
} catch (SoapFaultWithTracingData $e) {
$tracingData = $e->getSoapResponseTracingData();
}
$this->assertEquals($this->mergeOptions(array('soap_version' => SOAP_1_2, 'encoding' => 'UTF-8', 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, 'user_agent' => 'BeSimpleSoap')), $builder->getSoapOptions()); self::assertNotContains('boundary=Part_', $tracingData->getLastRequestHeaders(), 'Headers should link to boundary');
self::assertNotContains('start="<part-', $tracingData->getLastRequestHeaders(), 'Headers should link to first MultiPart');
self::assertContains('action="', $tracingData->getLastRequestHeaders(), 'Headers should contain SOAP action');
self::assertEquals(
file_get_contents(__DIR__.'/soapRequestWithNoAttachments.request'),
$tracingData->getLastRequest(),
'Requests must match'
);
}
/**
* @see This test needs a working SWA endpoint. Examine Tests/Mock directory for details
*/
public function testSoapCallSwaWithAttachmentsOnResponse()
{
$soapClient = $this->getSoapBuilder()->buildWithSoapHeader(
SoapClientOptionsBuilder::createWithTracing(),
SoapOptionsBuilder::createSwaWithClassMapV11(
self::TEST_REMOTE_WSDL_SWA,
new ClassMap([
'GenerateTestRequest' => GenerateTestRequest::class,
]),
SoapOptions::SOAP_CACHE_TYPE_DISK,
__DIR__ . '/../../../../cache'
),
new SoapHeader('http://schema.testcase', 'SoapHeader', [
'user' => 'admin',
])
);
$generateTestRequest = new GenerateTestRequest();
$generateTestRequest->salutation = 'World';
$soapResponse = $soapClient->soapCall('generateTest', [$generateTestRequest]);
$attachments = $soapResponse->getAttachments();
self::assertContains('</generateTestReturn>', $soapResponse->getResponseContent());
self::assertTrue($soapResponse->hasAttachments());
self::assertCount(1, $attachments);
$firstAttachment = reset($attachments);
self::assertEquals('text/plain', $firstAttachment->getHeader('Content-Type'));
file_put_contents(self::CACHE_DIR . '/testSoapCallSwaWithAttachmentsOnResponse.xml', $soapResponse->getContent());
file_put_contents(self::CACHE_DIR . '/testSoapCallSwaWithAttachmentsOnResponse.txt', $firstAttachment->getContent());
} }
private function getSoapBuilder() private function getSoapBuilder()
@ -126,8 +299,41 @@ class SoapClientBuilderTest extends \PHPUnit_Framework_TestCase
return new SoapClientBuilder(); return new SoapClientBuilder();
} }
private function mergeOptions(array $options) public function removeOneTimeData($string)
{ {
return array_merge($this->defaultOptions, $options); $contentId = $this->getContentId($string);
$multiPartBoundary = $this->getMultiPartBoundary($string);
return str_replace(
$contentId,
'{content-id-placeholder}',
str_replace(
$multiPartBoundary,
'{multipart-boundary-placeholder}',
$string
)
);
}
private function getMultiPartBoundary($string)
{
$realMultiParts = null;
preg_match('/Part\_[0-9]{2}\_[a-zA-Z0-9]{13}\.[a-zA-Z0-9]{13}/', $string, $realMultiParts);
if (count($realMultiParts) > 0) {
return $realMultiParts[0];
}
throw new Exception('Could not find real MultiPart boundary');
}
private function getContentId($string)
{
$realContentIds = null;
preg_match('/part\-[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}\@response\.info/', $string, $realContentIds);
if (count($realContentIds) > 0) {
return $realContentIds[0];
}
throw new Exception('Could not find real contentId');
} }
} }

View File

@ -0,0 +1,380 @@
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.webserviceX.NET" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://www.webserviceX.NET" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK Postcode,Town,County and Validate UK Address</wsdl:documentation>
<wsdl:types>
<s:schema elementFormDefault="qualified" targetNamespace="http://www.webserviceX.NET">
<s:element name="GetUKLocationByCounty">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="County" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetUKLocationByCountyResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUKLocationByCountyResult" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetUKLocationByTown">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="Town" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetUKLocationByTownResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUKLocationByTownResult" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetUKLocationByPostCode">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="PostCode" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="GetUKLocationByPostCodeResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUKLocationByPostCodeResult" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="ValidateUKAddress">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="Town" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="County" type="s:string" />
<s:element minOccurs="0" maxOccurs="1" name="PostCode" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="ValidateUKAddressResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="ValidateUKAddressResult" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="string" nillable="true" type="s:string" />
</s:schema>
</wsdl:types>
<wsdl:message name="GetUKLocationByCountySoapIn">
<wsdl:part name="parameters" element="tns:GetUKLocationByCounty" />
</wsdl:message>
<wsdl:message name="GetUKLocationByCountySoapOut">
<wsdl:part name="parameters" element="tns:GetUKLocationByCountyResponse" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownSoapIn">
<wsdl:part name="parameters" element="tns:GetUKLocationByTown" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownSoapOut">
<wsdl:part name="parameters" element="tns:GetUKLocationByTownResponse" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeSoapIn">
<wsdl:part name="parameters" element="tns:GetUKLocationByPostCode" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeSoapOut">
<wsdl:part name="parameters" element="tns:GetUKLocationByPostCodeResponse" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressSoapIn">
<wsdl:part name="parameters" element="tns:ValidateUKAddress" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressSoapOut">
<wsdl:part name="parameters" element="tns:ValidateUKAddressResponse" />
</wsdl:message>
<wsdl:message name="GetUKLocationByCountyHttpGetIn">
<wsdl:part name="County" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByCountyHttpGetOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownHttpGetIn">
<wsdl:part name="Town" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownHttpGetOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeHttpGetIn">
<wsdl:part name="PostCode" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeHttpGetOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressHttpGetIn">
<wsdl:part name="Town" type="s:string" />
<wsdl:part name="County" type="s:string" />
<wsdl:part name="PostCode" type="s:string" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressHttpGetOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByCountyHttpPostIn">
<wsdl:part name="County" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByCountyHttpPostOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownHttpPostIn">
<wsdl:part name="Town" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByTownHttpPostOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeHttpPostIn">
<wsdl:part name="PostCode" type="s:string" />
</wsdl:message>
<wsdl:message name="GetUKLocationByPostCodeHttpPostOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressHttpPostIn">
<wsdl:part name="Town" type="s:string" />
<wsdl:part name="County" type="s:string" />
<wsdl:part name="PostCode" type="s:string" />
</wsdl:message>
<wsdl:message name="ValidateUKAddressHttpPostOut">
<wsdl:part name="Body" element="tns:string" />
</wsdl:message>
<wsdl:portType name="UKLocationSoap">
<wsdl:operation name="GetUKLocationByCounty">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial County</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByCountySoapIn" />
<wsdl:output message="tns:GetUKLocationByCountySoapOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial Town</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByTownSoapIn" />
<wsdl:output message="tns:GetUKLocationByTownSoapOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by Postcode(First Section of Post Code)</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByPostCodeSoapIn" />
<wsdl:output message="tns:GetUKLocationByPostCodeSoapOut" />
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Validate UK address,Use First Section of Poscode for Postcode atribute</wsdl:documentation>
<wsdl:input message="tns:ValidateUKAddressSoapIn" />
<wsdl:output message="tns:ValidateUKAddressSoapOut" />
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="UKLocationHttpGet">
<wsdl:operation name="GetUKLocationByCounty">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial County</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByCountyHttpGetIn" />
<wsdl:output message="tns:GetUKLocationByCountyHttpGetOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial Town</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByTownHttpGetIn" />
<wsdl:output message="tns:GetUKLocationByTownHttpGetOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by Postcode(First Section of Post Code)</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByPostCodeHttpGetIn" />
<wsdl:output message="tns:GetUKLocationByPostCodeHttpGetOut" />
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Validate UK address,Use First Section of Poscode for Postcode atribute</wsdl:documentation>
<wsdl:input message="tns:ValidateUKAddressHttpGetIn" />
<wsdl:output message="tns:ValidateUKAddressHttpGetOut" />
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="UKLocationHttpPost">
<wsdl:operation name="GetUKLocationByCounty">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial County</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByCountyHttpPostIn" />
<wsdl:output message="tns:GetUKLocationByCountyHttpPostOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by full /partial Town</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByTownHttpPostIn" />
<wsdl:output message="tns:GetUKLocationByTownHttpPostOut" />
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK town,Postcode and County by Postcode(First Section of Post Code)</wsdl:documentation>
<wsdl:input message="tns:GetUKLocationByPostCodeHttpPostIn" />
<wsdl:output message="tns:GetUKLocationByPostCodeHttpPostOut" />
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Validate UK address,Use First Section of Poscode for Postcode atribute</wsdl:documentation>
<wsdl:input message="tns:ValidateUKAddressHttpPostIn" />
<wsdl:output message="tns:ValidateUKAddressHttpPostOut" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="UKLocationSoap" type="tns:UKLocationSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="GetUKLocationByCounty">
<soap:operation soapAction="http://www.webserviceX.NET/GetUKLocationByCounty" style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<soap:operation soapAction="http://www.webserviceX.NET/GetUKLocationByTown" style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<soap:operation soapAction="http://www.webserviceX.NET/GetUKLocationByPostCode" style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<soap:operation soapAction="http://www.webserviceX.NET/ValidateUKAddress" style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="UKLocationSoap12" type="tns:UKLocationSoap">
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="GetUKLocationByCounty">
<soap12:operation soapAction="http://www.webserviceX.NET/GetUKLocationByCounty" style="document" />
<wsdl:input>
<soap12:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap12:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<soap12:operation soapAction="http://www.webserviceX.NET/GetUKLocationByTown" style="document" />
<wsdl:input>
<soap12:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap12:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<soap12:operation soapAction="http://www.webserviceX.NET/GetUKLocationByPostCode" style="document" />
<wsdl:input>
<soap12:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap12:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<soap12:operation soapAction="http://www.webserviceX.NET/ValidateUKAddress" style="document" />
<wsdl:input>
<soap12:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap12:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="UKLocationHttpGet" type="tns:UKLocationHttpGet">
<http:binding verb="GET" />
<wsdl:operation name="GetUKLocationByCounty">
<http:operation location="/GetUKLocationByCounty" />
<wsdl:input>
<http:urlEncoded />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<http:operation location="/GetUKLocationByTown" />
<wsdl:input>
<http:urlEncoded />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<http:operation location="/GetUKLocationByPostCode" />
<wsdl:input>
<http:urlEncoded />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<http:operation location="/ValidateUKAddress" />
<wsdl:input>
<http:urlEncoded />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="UKLocationHttpPost" type="tns:UKLocationHttpPost">
<http:binding verb="POST" />
<wsdl:operation name="GetUKLocationByCounty">
<http:operation location="/GetUKLocationByCounty" />
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded" />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByTown">
<http:operation location="/GetUKLocationByTown" />
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded" />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="GetUKLocationByPostCode">
<http:operation location="/GetUKLocationByPostCode" />
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded" />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="ValidateUKAddress">
<http:operation location="/ValidateUKAddress" />
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded" />
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="UKLocation">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get UK Postcode,Town,County and Validate UK Address</wsdl:documentation>
<wsdl:port name="UKLocationSoap" binding="tns:UKLocationSoap">
<soap:address location="http://www.webservicex.net/uklocation.asmx" />
</wsdl:port>
<wsdl:port name="UKLocationSoap12" binding="tns:UKLocationSoap12">
<soap12:address location="http://www.webservicex.net/uklocation.asmx" />
</wsdl:port>
<wsdl:port name="UKLocationHttpGet" binding="tns:UKLocationHttpGet">
<http:address location="http://www.webservicex.net/uklocation.asmx" />
</wsdl:port>
<wsdl:port name="UKLocationHttpPost" binding="tns:UKLocationHttpPost">
<http:address location="http://www.webservicex.net/uklocation.asmx" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://www.webserviceX.NET"><env:Body><ns1:GetUKLocationByCounty><ns1:County>London</ns1:County></ns1:GetUKLocationByCounty></env:Body></env:Envelope>

View File

@ -0,0 +1,24 @@
--Part_10_589b2dcf4f7fb.589b2dcf4f804
Content-Type: application/soap+xml; charset=utf-8
Content-Transfer-Encoding: 8bit
Content-ID: <part-7e61b8e0-f84d-4cf7-8043-3c27d90767af@response.info>
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://www.webserviceX.NET"><env:Body><ns1:GetUKLocationByCounty><ns1:County>London</ns1:County></ns1:GetUKLocationByCounty></env:Body></env:Envelope>
--Part_10_589b2dcf4f7fb.589b2dcf4f804
Content-Type: application/pdf; charset=utf-8
Content-Transfer-Encoding: binary
Content-ID: <first-file.txt>
Content-Location: first-file.txt
hello world
--Part_10_589b2dcf4f7fb.589b2dcf4f804
Content-Type: application/pdf; charset=utf-8
Content-Transfer-Encoding: binary
Content-ID: <second-file.txt>
Content-Location: second-file.txt
hello world
--Part_10_589b2dcf4f7fb.589b2dcf4f804--

View File

@ -12,8 +12,13 @@
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
use BeSimple\SoapClient\Curl\Curl;
use BeSimple\SoapCommon\Cache; use BeSimple\SoapCommon\Cache;
use BeSimple\SoapCommon\Helper; use BeSimple\SoapCommon\Helper;
use DOMDocument;
use DOMElement;
use DOMXPath;
use Exception;
/** /**
* Downloads WSDL files with cURL. Uses the WSDL_CACHE_* constants and the * Downloads WSDL files with cURL. Uses the WSDL_CACHE_* constants and the
@ -25,148 +30,179 @@ use BeSimple\SoapCommon\Helper;
*/ */
class WsdlDownloader class WsdlDownloader
{ {
protected $curl;
protected $resolveRemoteIncludes = true;
protected $cacheEnabled;
protected $cacheDir;
protected $cacheTtl;
/** /**
* @param Curl $curl * @param Curl $curl
* @param int $cacheWsdl = Cache::TYPE_NONE|Cache::WSDL_CACHE_DISK|Cache::WSDL_CACHE_BOTH|Cache::WSDL_CACHE_MEMORY * @param string $wsdlPath WSDL file URL/path
* @param int $wsdCacheType = Cache::TYPE_NONE|Cache::WSDL_CACHE_DISK|Cache::WSDL_CACHE_BOTH|Cache::WSDL_CACHE_MEMORY
* @param boolean $resolveRemoteIncludes * @param boolean $resolveRemoteIncludes
*/
public function __construct(Curl $curl, $cacheWsdl, $resolveRemoteIncludes = true)
{
$this->curl = $curl;
$this->resolveRemoteIncludes = $resolveRemoteIncludes;
$this->cacheEnabled = $cacheWsdl === Cache::TYPE_NONE ? Cache::DISABLED : Cache::ENABLED == Cache::isEnabled();
$this->cacheDir = Cache::getDirectory();
$this->cacheTtl = Cache::getLifetime();
}
/**
* Download given WSDL file and return name of cache file.
*
* @param string $wsdl WSDL file URL/path
*
* @return string * @return string
*/ */
public function download($wsdl) public function getWsdlPath(Curl $curl, $wsdlPath, $wsdCacheType, $resolveRemoteIncludes = true)
{ {
// download and cache remote WSDL files or local ones where we want to $isRemoteFile = $this->isRemoteFile($wsdlPath);
// resolve remote XSD includes $isCacheEnabled = $wsdCacheType === Cache::TYPE_NONE ? false : Cache::isEnabled();
$isRemoteFile = $this->isRemoteFile($wsdl); if ($isCacheEnabled === true) {
if ($isRemoteFile || $this->resolveRemoteIncludes) { $cacheFilePath = Cache::getDirectory().DIRECTORY_SEPARATOR.'wsdl_'.md5($wsdlPath).'.cache';
$cacheFilePath = $this->cacheDir.DIRECTORY_SEPARATOR.'wsdl_'.md5($wsdl).'.cache'; $isCacheExisting = file_exists($cacheFilePath);
if ($isCacheExisting) {
if (!$this->cacheEnabled || !file_exists($cacheFilePath) || (filemtime($cacheFilePath) + $this->cacheTtl) < time()) { $fileModificationTime = filemtime($cacheFilePath);
if ($isRemoteFile) { if ($fileModificationTime === false) {
// execute request throw new Exception('File modification time could not be get for wsdl path: ' . $cacheFilePath);
$responseSuccessfull = $this->curl->exec($wsdl);
// get content
if ($responseSuccessfull) {
$response = $this->curl->getResponseBody();
if ($this->resolveRemoteIncludes) {
$this->resolveRemoteIncludes($response, $cacheFilePath, $wsdl);
} else {
file_put_contents($cacheFilePath, $response);
}
} else {
throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'");
}
} elseif (file_exists($wsdl)) {
$response = file_get_contents($wsdl);
$this->resolveRemoteIncludes($response, $cacheFilePath);
} else {
throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'");
} }
$isCacheValid = ($fileModificationTime + Cache::getLifetime()) >= time();
} else {
$isCacheExisting = $isCacheValid = false;
}
if ($isCacheExisting === false || $isCacheValid === false) {
$this->writeCacheFile($curl, $wsdCacheType, $wsdlPath, $cacheFilePath, $resolveRemoteIncludes, $isRemoteFile);
} }
return $cacheFilePath; return $this->getLocalWsdlPath($cacheFilePath);
} elseif (file_exists($wsdl)) {
return realpath($wsdl);
}
throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); } else {
if ($isRemoteFile === true) {
return $wsdlPath;
}
return $this->getLocalWsdlPath($wsdlPath);
}
}
private function writeCacheFile(Curl $curl, $cacheType, $wsdlPath, $cacheFilePath, $resolveRemoteIncludes, $isRemoteFile)
{
if ($isRemoteFile === true) {
$curlResponse = $curl->executeCurlWithCachedSession($wsdlPath);
if ($curlResponse->curlStatusSuccess()) {
if (mb_strlen($curlResponse->getResponseBody()) === 0) {
throw new Exception('Could not write WSDL cache file: curl response empty');
}
if ($resolveRemoteIncludes === true) {
$document = $this->getXmlFileDOMDocument($curl, $cacheType, $curlResponse->getResponseBody(), $wsdlPath);
$this->saveXmlDOMDocument($document, $cacheFilePath);
} else {
file_put_contents($cacheFilePath, $curlResponse->getResponseBody());
}
} else {
throw new Exception('Could not write WSDL cache file: Download failed with message: '.$curlResponse->getCurlErrorMessage());
}
} else {
if (file_exists($wsdlPath)) {
$document = $this->getXmlFileDOMDocument($curl, $cacheType, file_get_contents($wsdlPath));
$this->saveXmlDOMDocument($document, $cacheFilePath);
} else {
throw new Exception('Could write WSDL cache file: local file does not exist: '.$wsdlPath);
}
}
}
private function getLocalWsdlPath($wsdlPath)
{
if (file_exists($wsdlPath)) {
return realpath($wsdlPath);
} else {
throw new Exception('Could not download WSDL: local file does not exist: '.$wsdlPath);
}
} }
/** /**
* Do we have a remote file? * @param string $wsdlPath File URL/path
*
* @param string $file File URL/path
*
* @return boolean * @return boolean
*/ */
private function isRemoteFile($file) private function isRemoteFile($wsdlPath)
{ {
// @parse_url to suppress E_WARNING for invalid urls $parsedUrlOrFalse = @parse_url($wsdlPath);
if (false !== $url = @parse_url($file)) { if ($parsedUrlOrFalse !== false) {
if (isset($url['scheme']) && 'http' === substr($url['scheme'], 0, 4)) { if (isset($parsedUrlOrFalse['scheme']) && substr($parsedUrlOrFalse['scheme'], 0, 4) === 'http') {
return true; return true;
} else {
return false;
} }
} }
return false; throw new Exception('Could not determine wsdlPath is remote: '.$wsdlPath);
} }
/** /**
* Resolves remote WSDL/XSD includes within the WSDL files. * Resolves remote WSDL/XSD includes within the WSDL files.
* *
* @param string $xml XML file * @param Curl $curl
* @param string $cacheFilePath Cache file name * @param int $cacheType
* @param string $xmlFileSource XML file contents
* @param boolean $parentFilePath Parent file name * @param boolean $parentFilePath Parent file name
* * @return DOMDocument
* @return void
*/ */
private function resolveRemoteIncludes($xml, $cacheFilePath, $parentFilePath = null) private function getXmlFileDOMDocument(Curl $curl, $cacheType, $xmlFileSource, $parentFilePath = null)
{ {
$doc = new \DOMDocument(); $document = new DOMDocument('1.0', 'utf-8');
$doc->loadXML($xml); if ($document->loadXML($xmlFileSource) === false) {
throw new Exception('Could not save downloaded WSDL cache: '.$xmlFileSource);
}
$xpath = new \DOMXPath($doc); $xpath = new DOMXPath($document);
$xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA); $this->updateXmlDocument($curl, $cacheType, $xpath, Helper::PFX_WSDL, Helper::NS_WSDL, 'location', $parentFilePath);
$xpath->registerNamespace(Helper::PFX_WSDL, Helper::NS_WSDL); $this->updateXmlDocument($curl, $cacheType, $xpath, Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA, 'schemaLocation', $parentFilePath);
// WSDL include/import return $document;
$query = './/'.Helper::PFX_WSDL.':include | .//'.Helper::PFX_WSDL.':import'; }
$nodes = $xpath->query($query);
private function saveXmlDOMDocument(DOMDocument $document, $cacheFilePath)
{
try {
$xmlContents = $document->saveXML();
if ($xmlContents === '') {
throw new Exception('Could not write WSDL cache file: DOMDocument returned empty XML file');
}
file_put_contents($cacheFilePath, $xmlContents);
} catch (Exception $e) {
unlink($cacheFilePath);
throw new Exception('Could not write WSDL cache file: save method returned error: ' . $e->getMessage());
}
}
private function updateXmlDocument(
Curl $curl,
$cacheType,
DOMXPath $xpath,
$schemaPrefix,
$schemaUrl,
$locationAttributeName,
$parentFilePath = null
) {
$xpath->registerNamespace($schemaPrefix, $schemaUrl);
$nodes = $xpath->query('.//'.$schemaPrefix.':include | .//'.$schemaPrefix.':import');
if ($nodes->length > 0) { if ($nodes->length > 0) {
foreach ($nodes as $node) { foreach ($nodes as $node) {
$location = $node->getAttribute('location'); /** @var DOMElement $node */
if ($this->isRemoteFile($location)) { $locationPath = $node->getAttribute($locationAttributeName);
$location = $this->download($location); if ($this->isRemoteFile($locationPath)) {
$node->setAttribute('location', $location); $node->setAttribute(
} elseif (null !== $parentFilePath) { $locationAttributeName,
$location = $this->resolveRelativePathInUrl($parentFilePath, $location); $this->getWsdlPath(
$location = $this->download($location); $curl,
$node->setAttribute('location', $location); $locationPath,
$cacheType,
true
)
);
} else if ($parentFilePath !== null) {
$node->setAttribute(
$locationAttributeName,
$this->getWsdlPath(
$curl,
$this->resolveRelativePathInUrl($parentFilePath, $locationPath),
$cacheType,
true
)
);
} }
} }
} }
// XML schema include/import
$query = './/'.Helper::PFX_XML_SCHEMA.':include | .//'.Helper::PFX_XML_SCHEMA.':import';
$nodes = $xpath->query($query);
if ($nodes->length > 0) {
foreach ($nodes as $node) {
if ($node->hasAttribute('schemaLocation')) {
$schemaLocation = $node->getAttribute('schemaLocation');
if ($this->isRemoteFile($schemaLocation)) {
$schemaLocation = $this->download($schemaLocation);
$node->setAttribute('schemaLocation', $schemaLocation);
} elseif (null !== $parentFilePath) {
$schemaLocation = $this->resolveRelativePathInUrl($parentFilePath, $schemaLocation);
$schemaLocation = $this->download($schemaLocation);
$node->setAttribute('schemaLocation', $schemaLocation);
}
}
}
}
$doc->save($cacheFilePath);
} }
/** /**
@ -205,9 +241,8 @@ class WsdlDownloader
// resolve /../ // resolve /../
foreach ($parts as $key => $part) { foreach ($parts as $key => $part) {
if ('..' === $part) { if ($part === '..') {
$keyToDelete = $key - 1; $keyToDelete = $key - 1;
while ($keyToDelete > 0) { while ($keyToDelete > 0) {
if (isset($parts[$keyToDelete])) { if (isset($parts[$keyToDelete])) {
unset($parts[$keyToDelete]); unset($parts[$keyToDelete]);

View File

@ -12,6 +12,8 @@
namespace BeSimple\SoapCommon; namespace BeSimple\SoapCommon;
use InvalidArgumentException;
/** /**
* @author Francis Besset <francis.besset@gmail.com> * @author Francis Besset <francis.besset@gmail.com>
*/ */
@ -32,22 +34,22 @@ class Cache
self::TYPE_DISK_MEMORY, self::TYPE_DISK_MEMORY,
]; ];
static public function getTypes() public static function getTypes()
{ {
return self::$types; return self::$types;
} }
static public function hasType($cacheType) public static function hasType($cacheType)
{ {
return in_array($cacheType, self::$types); return in_array($cacheType, self::$types);
} }
static public function isEnabled() public static function isEnabled()
{ {
return self::iniGet('soap.wsdl_cache_enabled'); return self::iniGet('soap.wsdl_cache_enabled') === '1';
} }
static public function setEnabled($enabled) public static function setEnabled($enabled)
{ {
if (!in_array($enabled, array(self::ENABLED, self::DISABLED), true)) { if (!in_array($enabled, array(self::ENABLED, self::DISABLED), true)) {
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
@ -56,60 +58,58 @@ class Cache
self::iniSet('soap.wsdl_cache_enabled', $enabled); self::iniSet('soap.wsdl_cache_enabled', $enabled);
} }
static public function getType() public static function getType()
{ {
return self::iniGet('soap.wsdl_cache'); return self::iniGet('soap.wsdl_cache');
} }
static public function setType($type) public static function setType($type)
{ {
if (!in_array($type, self::getTypes(), true)) { if (!in_array($type, self::getTypes(), true)) {
throw new \InvalidArgumentException('The cache type has to be either Cache::TYPE_NONE, Cache::TYPE_DISK, Cache::TYPE_MEMORY or Cache::TYPE_DISK_MEMORY'); throw new InvalidArgumentException(
'The cache type has to be either Cache::TYPE_NONE, Cache::TYPE_DISK, Cache::TYPE_MEMORY or Cache::TYPE_DISK_MEMORY'
);
} }
self::iniSet('soap.wsdl_cache', $type); self::iniSet('soap.wsdl_cache', $type);
} }
static public function getDirectory() public static function getDirectory()
{ {
return self::iniGet('soap.wsdl_cache_dir'); return self::iniGet('soap.wsdl_cache_dir');
} }
static public function setDirectory($directory) public static function setDirectory($directory)
{ {
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
self::iniSet('soap.wsdl_cache_dir', $directory); self::iniSet('soap.wsdl_cache_dir', $directory);
} }
static public function getLifetime() public static function getLifetime()
{ {
return self::iniGet('soap.wsdl_cache_ttl'); return self::iniGet('soap.wsdl_cache_ttl');
} }
static public function setLifetime($lifetime) public static function setLifetime($lifetime)
{ {
self::iniSet('soap.wsdl_cache_ttl', $lifetime); self::iniSet('soap.wsdl_cache_ttl', $lifetime);
} }
static public function getLimit() public static function getLimit()
{ {
return self::iniGet('soap.wsdl_cache_limit'); return self::iniGet('soap.wsdl_cache_limit');
} }
static public function setLimit($limit) public static function setLimit($limit)
{ {
self::iniSet('soap.wsdl_cache_limit', $limit); self::iniSet('soap.wsdl_cache_limit', $limit);
} }
static protected function iniGet($key) protected static function iniGet($key)
{ {
return ini_get($key); return ini_get($key);
} }
static protected function iniSet($key, $value) protected static function iniSet($key, $value)
{ {
ini_set($key, $value); ini_set($key, $value);
} }

View File

@ -22,11 +22,6 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
*/ */
class MtomTypeConverter implements TypeConverterInterface class MtomTypeConverter implements TypeConverterInterface
{ {
/**
* @var \BeSimple\SoapCommon\SoapKernel $soapKernel SoapKernel instance
*/
protected $soapKernel = null;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@ -0,0 +1,10 @@
<?php
namespace BeSimple\SoapCommon\Fault;
class SoapFaultEnum
{
const SOAP_FAULT_WSDL = 'wsdl';
const SOAP_FAULT_HTTP = 'http';
const SOAP_FAULT_SOAP_CLIENT_ERROR = 'soap-client-error';
}

View File

@ -181,4 +181,9 @@ class MultiPart extends PartHeader
{ {
return 'multipart-boundary-' . Helper::generateUUID() . '@response.info'; return 'multipart-boundary-' . Helper::generateUUID() . '@response.info';
} }
public function getMainPartContentId()
{
return $this->mainPartContentId;
}
} }

View File

@ -12,183 +12,241 @@
namespace BeSimple\SoapCommon\Mime; namespace BeSimple\SoapCommon\Mime;
use BeSimple\SoapCommon\Mime\Parser\ContentTypeParser;
use BeSimple\SoapCommon\Mime\Parser\ParsedPart;
use BeSimple\SoapCommon\Mime\Parser\ParsedPartList;
use Exception;
/** /**
* Simple Multipart-Mime parser. * Simple Multipart-Mime parser.
* *
* @author Andreas Schamberger <mail@andreass.net> * @author Andreas Schamberger <mail@andreass.net>
* @author Petr Bechyne <mail@petrbechyne.com>
*/ */
class Parser class Parser
{ {
const HAS_HTTP_REQUEST_HEADERS = true;
const HAS_NO_HTTP_REQUEST_HEADERS = false;
/** /**
* Parse the given Mime-Message and return a \BeSimple\SoapCommon\Mime\MultiPart object. * Parse the given Mime-Message and return a \BeSimple\SoapCommon\Mime\MultiPart object.
* *
* @param string $mimeMessage Mime message string * @param string $mimeMessage Mime message string
* @param array(string=>string) $headers Array of header elements (e.g. coming from http request) * @param string[] $headers array(string=>string) of header elements (e.g. coming from http request)
* *
* @return \BeSimple\SoapCommon\Mime\MultiPart * @return \BeSimple\SoapCommon\Mime\MultiPart
*/ */
public static function parseMimeMessage($mimeMessage, array $headers = []) public static function parseMimeMessage($mimeMessage, array $headers = [])
{ {
$boundary = null; $multiPart = new MultiPart();
$start = null; $mimeMessageLines = preg_split("/(\n)/", $mimeMessage);
$multipart = new MultiPart();
$hitFirstBoundary = false;
$inHeader = true;
// add given headers, e.g. coming from HTTP headers // add given headers, e.g. coming from HTTP headers
if (count($headers) > 0) { if (count($headers) > 0) {
foreach ($headers as $name => $value) { self::setMultiPartHeaders($multiPart, $headers);
if ($name === 'Content-Type') { $hasHttpRequestHeaders = self::HAS_HTTP_REQUEST_HEADERS;
self::parseContentTypeHeader($multipart, $name, $value); } else {
$boundary = $multipart->getHeader('Content-Type', 'boundary'); $hasHttpRequestHeaders = self::HAS_NO_HTTP_REQUEST_HEADERS;
$start = $multipart->getHeader('Content-Type', 'start');
} else {
$multipart->setHeader($name, $value);
}
}
$inHeader = false;
} }
$content = ''; if (self::hasBoundary($mimeMessageLines)) {
$currentPart = $multipart; $parsedPartList = self::getPartsFromMimeMessageLines(
$lines = preg_split("/(\r\n)|(\n)/", $mimeMessage); $multiPart,
if (self::hasBoundary($lines)) { $mimeMessageLines,
foreach ($lines as $line) { $hasHttpRequestHeaders
// ignore http status code and POST * );
if (substr($line, 0, 5) == 'HTTP/' || substr($line, 0, 4) == 'POST') { if ($parsedPartList->hasParts() === false) {
throw new Exception(
'Could not parse MimeMessage: no Parts for MultiPart given'
);
}
if ($parsedPartList->hasExactlyOneMainPart() === false) {
throw new Exception(
sprintf(
'Could not parse MimeMessage %s HTTP headers: unexpected count of main ParsedParts: %s (total: %d)',
$hasHttpRequestHeaders ? 'with' : 'w/o',
implode(', ', $parsedPartList->getPartContentIds()),
$parsedPartList->getMainPartCount()
)
);
}
self::appendPartsToMultiPart(
$parsedPartList,
$multiPart
);
} else {
self::appendSingleMainPartToMultiPart(new Part($mimeMessage), $multiPart);
}
return $multiPart;
}
/**
* @param MultiPart $multiPart
* @param string[] $mimeMessageLines
* @param bool $hasHttpHeaders = self::HAS_HTTP_REQUEST_HEADERS|self::HAS_NO_HTTP_REQUEST_HEADERS
* @return ParsedPartList
*/
private static function getPartsFromMimeMessageLines(
MultiPart $multiPart,
array $mimeMessageLines,
$hasHttpHeaders
) {
$parsedParts = [];
$contentTypeBoundary = $multiPart->getHeader('Content-Type', 'boundary');
$contentTypeContentIdStart = $multiPart->getHeader('Content-Type', 'start');
$currentPart = $multiPart;
$messagePartStringContent = '';
$inHeader = $hasHttpHeaders;
$hitFirstBoundary = false;
foreach ($mimeMessageLines as $mimeMessageLine) {
// ignore http status code and POST *
if (substr($mimeMessageLine, 0, 5) == 'HTTP/' || substr($mimeMessageLine, 0, 4) == 'POST') {
continue;
}
if (isset($currentHeader)) {
if (isset($mimeMessageLine[0]) && ($mimeMessageLine[0] === ' ' || $mimeMessageLine[0] === "\t")) {
$currentHeader .= $mimeMessageLine;
continue; continue;
} }
if (isset($currentHeader)) { if (strpos($currentHeader, ':') !== false) {
if (isset($line[0]) && ($line[0] === ' ' || $line[0] === "\t")) { list($headerName, $headerValue) = explode(':', $currentHeader, 2);
$currentHeader .= $line; $headerValue = iconv_mime_decode($headerValue, 0, Part::CHARSET_UTF8);
continue; $parsedMimeHeaders = ContentTypeParser::parseContentTypeHeader($headerName, $headerValue);
foreach ($parsedMimeHeaders as $parsedMimeHeader) {
$currentPart->setHeader(
$parsedMimeHeader->getName(),
$parsedMimeHeader->getValue(),
$parsedMimeHeader->getSubValue()
);
} }
if (strpos($currentHeader, ':') !== false) { $contentTypeBoundary = $multiPart->getHeader('Content-Type', 'boundary');
list($headerName, $headerValue) = explode(':', $currentHeader, 2); $contentTypeContentIdStart = $multiPart->getHeader('Content-Type', 'start');
$headerValue = iconv_mime_decode($headerValue, 0, 'utf-8');
if (strpos($headerValue, ';') !== false) {
self::parseContentTypeHeader($currentPart, $headerName, $headerValue);
$boundary = $multipart->getHeader('Content-Type', 'boundary');
$start = $multipart->getHeader('Content-Type', 'start');
} else {
$currentPart->setHeader($headerName, trim($headerValue));
}
}
unset($currentHeader);
} }
if ($inHeader) { unset($currentHeader);
if (trim($line) == '') { }
$inHeader = false; if ($inHeader === true) {
continue; if (trim($mimeMessageLine) == '') {
} $inHeader = false;
$currentHeader = $line;
continue; continue;
} else { }
if (self::isBoundary($line)) { $currentHeader = $mimeMessageLine;
if (strcmp(trim($line), '--' . $boundary) === 0) { continue;
if ($currentPart instanceof Part) { } else {
$content = substr($content, 0, -1); if (self::isBoundary($mimeMessageLine)) {
self::decodeContent($currentPart, $content); if (self::isMiddleBoundary($mimeMessageLine, $contentTypeBoundary)) {
// check if there is a start parameter given, if not set first part if ($currentPart instanceof Part) {
$isMain = (is_null($start) || $start == $currentPart->getHeader('Content-ID')) ? true : false; $currentPartContent = self::decodeContent(
if ($isMain === true) { $currentPart,
$start = $currentPart->getHeader('Content-ID'); substr($messagePartStringContent, 0, -1)
} );
$multipart->addPart($currentPart, $isMain); $currentPart->setContent($currentPartContent);
}
$currentPart = new Part();
$hitFirstBoundary = true;
$inHeader = true;
$content = '';
} elseif (strcmp(trim($line), '--' . $boundary . '--') === 0) {
$content = substr($content, 0, -1);
self::decodeContent($currentPart, $content);
// check if there is a start parameter given, if not set first part // check if there is a start parameter given, if not set first part
$isMain = (is_null($start) || $start == $currentPart->getHeader('Content-ID')) ? true : false; if ($contentTypeContentIdStart === null || $currentPart->hasContentId($contentTypeContentIdStart) === true) {
if ($isMain === true) { $contentTypeContentIdStart = $currentPart->getHeader('Content-ID');
$start = $currentPart->getHeader('Content-ID'); $parsedParts[] = new ParsedPart($currentPart, ParsedPart::PART_IS_MAIN);
} else {
$parsedParts[] = new ParsedPart($currentPart, ParsedPart::PART_IS_NOT_MAIN);
} }
$multipart->addPart($currentPart, $isMain);
$content = '';
} }
$currentPart = new Part();
$hitFirstBoundary = true;
$inHeader = true;
$messagePartStringContent = '';
} else if (self::isLastBoundary($mimeMessageLine, $contentTypeBoundary)) {
$currentPartContent = self::decodeContent(
$currentPart,
substr($messagePartStringContent, 0, -1)
);
$currentPart->setContent($currentPartContent);
// check if there is a start parameter given, if not set first part
if ($contentTypeContentIdStart === null || $currentPart->hasContentId($contentTypeContentIdStart) === true) {
$contentTypeContentIdStart = $currentPart->getHeader('Content-ID');
$parsedParts[] = new ParsedPart($currentPart, ParsedPart::PART_IS_MAIN);
} else {
$parsedParts[] = new ParsedPart($currentPart, ParsedPart::PART_IS_NOT_MAIN);
}
$messagePartStringContent = '';
} else { } else {
if ($hitFirstBoundary === false) { // else block migrated from https://github.com/progmancod/BeSimpleSoap/commit/bf9437e3bcf35c98c6c2f26aca655ec3d3514694
if (trim($line) !== '') { // be careful to replace \r\n with \n
$inHeader = true; $messagePartStringContent .= $mimeMessageLine . "\n";
$currentHeader = $line;
continue;
}
}
$content .= $line . "\n";
} }
} else {
if ($hitFirstBoundary === false) {
if (trim($mimeMessageLine) !== '') {
$inHeader = true;
$currentHeader = $mimeMessageLine;
continue;
}
}
$messagePartStringContent .= $mimeMessageLine . "\n";
} }
} }
} else {
$multipart->addPart(new Part($mimeMessage), true);
} }
return $multipart; return new ParsedPartList($parsedParts);
} }
/** /**
* Parse a "Content-Type" header with multiple sub values. * @param ParsedPartList $parsedPartList
* e.g. Content-Type: multipart/related; boundary=boundary; type=text/xml; * @param MultiPart $multiPart
* start="<123@abc>"
*
* Based on: https://labs.omniti.com/alexandria/trunk/OmniTI/Mail/Parser.php
*
* @param \BeSimple\SoapCommon\Mime\PartHeader $part Header part
* @param string $headerName Header name
* @param string $headerValue Header value
*
*/ */
private static function parseContentTypeHeader(PartHeader $part, $headerName, $headerValue) private static function appendPartsToMultiPart(ParsedPartList $parsedPartList, MultiPart $multiPart)
{ {
if (strpos($headerValue, ';')) { foreach ($parsedPartList->getParts() as $parsedPart) {
list($value, $remainder) = explode(';', $headerValue, 2); $multiPart->addPart(
$value = trim($value); $parsedPart->getPart(),
$part->setHeader($headerName, $value); $parsedPart->isMain()
$remainder = trim($remainder); );
while (strlen($remainder) > 0) { }
if (!preg_match('/^([a-zA-Z0-9_-]+)=(.{1})/', $remainder, $matches)) { }
break;
private static function appendSingleMainPartToMultiPart(Part $part, MultiPart $multiPart)
{
$multiPart->addPart($part, true);
}
private static function setMultiPartHeaders(MultiPart $multiPart, $headers)
{
foreach ($headers as $name => $value) {
if ($name === 'Content-Type') {
$parsedMimeHeaders = ContentTypeParser::parseContentTypeHeader($name, $value);
foreach ($parsedMimeHeaders as $parsedMimeHeader) {
$multiPart->setHeader(
$parsedMimeHeader->getName(),
$parsedMimeHeader->getValue(),
$parsedMimeHeader->getSubValue()
);
} }
$name = $matches[1]; } else {
$delimiter = $matches[2]; $multiPart->setHeader($name, $value);
$remainder = substr($remainder, strlen($name) + 1);
if (!preg_match('/([^;]+)(;)?(\s|$)?/', $remainder, $matches)) {
break;
}
$value = rtrim($matches[1], ';');
if ($delimiter == "'" || $delimiter == '"') {
$value = trim($value, $delimiter);
}
$part->setHeader($headerName, $name, $value);
$remainder = substr($remainder, strlen($matches[0]));
} }
} else {
$part->setHeader($headerName, $headerValue);
} }
} }
/** /**
* Decodes the content of a Mime part. * Decodes the content of a Mime part
*
* @param \BeSimple\SoapCommon\Mime\Part $part Part to add content
* @param string $content Content to decode
* *
* @param Part $part Part to add content
* @param string $partStringContent Content to decode
* @return string $partStringContent decodedContent
*/ */
private static function decodeContent(Part $part, $content) private static function decodeContent(Part $part, $partStringContent)
{ {
$encoding = strtolower($part->getHeader('Content-Transfer-Encoding')); $encoding = strtolower($part->getHeader('Content-Transfer-Encoding'));
$charset = strtolower($part->getHeader('Content-Type', 'charset')); $charset = strtolower($part->getHeader('Content-Type', 'charset'));
if ($encoding == Part::ENCODING_BASE64) {
$content = base64_decode($content); if ($encoding === Part::ENCODING_BASE64) {
} elseif ($encoding == Part::ENCODING_QUOTED_PRINTABLE) { $partStringContent = base64_decode($partStringContent);
$content = quoted_printable_decode($content); } else if ($encoding === Part::ENCODING_QUOTED_PRINTABLE) {
$partStringContent = quoted_printable_decode($partStringContent);
} }
if ($charset != 'utf-8') {
$content = iconv($charset, 'utf-8', $content); if ($charset !== Part::CHARSET_UTF8) {
return iconv($charset, Part::CHARSET_UTF8, $partStringContent);
} }
$part->setContent($content);
return $partStringContent;
} }
private static function hasBoundary(array $lines) private static function hasBoundary(array $lines)
@ -203,8 +261,18 @@ class Parser
return false; return false;
} }
private static function isBoundary($line) private static function isBoundary($mimeMessageLine)
{ {
return strlen($line) > 0 && $line[0] === "-"; return strlen($mimeMessageLine) > 0 && $mimeMessageLine[0] === "-";
}
private static function isMiddleBoundary($mimeMessageLine, $contentTypeBoundary)
{
return strcmp(trim($mimeMessageLine), '--'.$contentTypeBoundary) === 0;
}
private static function isLastBoundary($mimeMessageLine, $contentTypeBoundary)
{
return strcmp(trim($mimeMessageLine), '--'.$contentTypeBoundary.'--') === 0;
} }
} }

View File

@ -0,0 +1,64 @@
<?php
namespace BeSimple\SoapCommon\Mime\Parser;
class ContentTypeParser
{
/**
* Parse a "Content-Type" header with multiple sub values.
* e.g. Content-Type: multipart/related; boundary=boundary; type=text/xml;
* start="<123@abc>"
*
* Based on: https://labs.omniti.com/alexandria/trunk/OmniTI/Mail/Parser.php
*
* @param string $headerName Header name
* @param string $headerValue Header value
* @return ParsedMimeHeader[]
*/
public static function parseContentTypeHeader($headerName, $headerValue)
{
if (self::isCompositeHeaderValue($headerValue)) {
$parsedMimeHeaders = self::parseCompositeValue($headerName, $headerValue);
} else {
$parsedMimeHeaders = [
new ParsedMimeHeader($headerName, trim($headerValue))
];
}
return $parsedMimeHeaders;
}
private static function parseCompositeValue($headerName, $headerValue)
{
$parsedMimeHeaders = [];
list($value, $remainder) = explode(';', $headerValue, 2);
$value = trim($value);
$parsedMimeHeaders[] = new ParsedMimeHeader($headerName, $value);
$remainder = trim($remainder);
while (strlen($remainder) > 0) {
if (!preg_match('/^([a-zA-Z0-9_-]+)=(.{1})/', $remainder, $matches)) {
break;
}
$name = $matches[1];
$delimiter = $matches[2];
$remainder = substr($remainder, strlen($name) + 1);
// preg_match migrated from https://github.com/progmancod/BeSimpleSoap/commit/6bc8f6a467616c934b0a9792f0efece55054db97
if (!preg_match('/([^;]+)(;\s*|\s*$)/', $remainder, $matches)) {
break;
}
$value = rtrim($matches[1], ';');
if ($delimiter == "'" || $delimiter == '"') {
$value = trim($value, $delimiter);
}
$remainder = substr($remainder, strlen($matches[0]));
$parsedMimeHeaders[] = new ParsedMimeHeader($headerName, $name, $value);
}
return $parsedMimeHeaders;
}
private static function isCompositeHeaderValue($headerValue)
{
return strpos($headerValue, ';');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace BeSimple\SoapCommon\Mime\Parser;
class ParsedMimeHeader
{
private $name;
private $value;
private $subValue;
/**
* @param string $name
* @param string $value
* @param string|null $subValue
*/
public function __construct($name, $value, $subValue = null)
{
$this->name = $name;
$this->value = $value;
$this->subValue = $subValue;
}
public function getName()
{
return $this->name;
}
public function getValue()
{
return $this->value;
}
public function getSubValue()
{
return $this->subValue;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace BeSimple\SoapCommon\Mime\Parser;
use BeSimple\SoapCommon\Mime\Part;
class ParsedPart
{
const PART_IS_MAIN = true;
const PART_IS_NOT_MAIN = false;
private $part;
private $isMain;
/**
* @param Part $part
* @param bool $isMain
*/
public function __construct(Part $part, $isMain)
{
$this->part = $part;
$this->isMain = $isMain;
}
public function getPart()
{
return $this->part;
}
public function isMain()
{
return $this->isMain;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace BeSimple\SoapCommon\Mime\Parser;
use Exception;
class ParsedPartList
{
private $parts;
/**
* @param ParsedPart[] $parts
*/
public function __construct(array $parts)
{
$this->parts = $parts;
}
public function getMainPartCount()
{
$mainPartsCount = 0;
foreach ($this->getParts() as $parsedPart) {
if ($parsedPart->isMain() === true) {
$mainPartsCount++;
}
}
return $mainPartsCount;
}
public function hasExactlyOneMainPart()
{
return $this->getMainPartCount() === 1;
}
public function getPartContentIds()
{
$partContentIds = [];
foreach ($this->getParts() as $parsedPart) {
$partContentIds[] = $parsedPart->getPart()->getContentId();
}
return $partContentIds;
}
public function getParts()
{
return $this->parts;
}
public function getPartCount()
{
return count($this->parts);
}
public function hasParts()
{
return $this->getPartCount() > 0;
}
}

View File

@ -28,57 +28,38 @@ use BeSimple\SoapCommon\Helper;
*/ */
class Part extends PartHeader class Part extends PartHeader
{ {
/**
* Encoding type base 64
*/
const ENCODING_BASE64 = 'base64'; const ENCODING_BASE64 = 'base64';
/**
* Encoding type binary
*/
const ENCODING_BINARY = 'binary'; const ENCODING_BINARY = 'binary';
/**
* Encoding type eight bit
*/
const ENCODING_EIGHT_BIT = '8bit'; const ENCODING_EIGHT_BIT = '8bit';
/**
* Encoding type seven bit
*/
const ENCODING_SEVEN_BIT = '7bit'; const ENCODING_SEVEN_BIT = '7bit';
/**
* Encoding type quoted printable
*/
const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
/** const CHARSET_UTF8 = 'utf-8';
* Content.
* const CONTENT_TYPE_STREAM = 'application/octet-stream';
* @var mixed const CONTENT_TYPE_PDF = 'application/pdf';
*/
/** @var mixed */
protected $content; protected $content;
/** /**
* Construct new mime object.
*
* @param mixed $content Content * @param mixed $content Content
* @param string $contentType Content type * @param string $contentType Content type
* @param string $charset Charset * @param string $charset Charset
* @param string $encoding Encoding * @param string $encoding Encoding
* @param string $contentId Content id * @param string $contentId Content id
*
*/ */
public function __construct($content = null, $contentType = 'application/octet-stream', $charset = null, $encoding = self::ENCODING_BINARY, $contentId = null) public function __construct(
{ $content = null,
$contentType = self::CONTENT_TYPE_STREAM,
$charset = self::CHARSET_UTF8,
$encoding = self::ENCODING_BINARY,
$contentId = null
) {
$this->content = $content; $this->content = $content;
$this->setHeader('Content-Type', $contentType); $this->setHeader('Content-Type', $contentType);
if (!is_null($charset)) { $this->setHeader('Content-Type', 'charset', $charset);
$this->setHeader('Content-Type', 'charset', $charset);
} else {
$this->setHeader('Content-Type', 'charset', 'utf-8');
}
$this->setHeader('Content-Transfer-Encoding', $encoding); $this->setHeader('Content-Transfer-Encoding', $encoding);
if (is_null($contentId)) { if (is_null($contentId)) {
$contentId = $this->generateContentId(); $contentId = $this->generateContentId();
@ -106,6 +87,16 @@ class Part extends PartHeader
return $this->content; return $this->content;
} }
public function hasContentId($contentTypeContentId)
{
return $contentTypeContentId === $this->getContentId();
}
public function getContentId()
{
return $this->getHeader('Content-ID');
}
/** /**
* Set mime content. * Set mime content.
* *
@ -137,10 +128,10 @@ class Part extends PartHeader
{ {
$encoding = strtolower($this->getHeader('Content-Transfer-Encoding')); $encoding = strtolower($this->getHeader('Content-Transfer-Encoding'));
$charset = strtolower($this->getHeader('Content-Type', 'charset')); $charset = strtolower($this->getHeader('Content-Type', 'charset'));
if ($charset != 'utf-8') { if ($charset !== self::CHARSET_UTF8) {
$content = iconv('utf-8', $charset . '//TRANSLIT', $this->content); $content = iconv(self::CHARSET_UTF8, $charset.'//TRANSLIT', $this->getContent());
} else { } else {
$content = $this->content; $content = $this->getContent();
} }
switch ($encoding) { switch ($encoding) {
case self::ENCODING_BASE64: case self::ENCODING_BASE64:

View File

@ -0,0 +1,33 @@
<?php
namespace BeSimple\SoapCommon\Mime;
use BeSimple\SoapBundle\Soap\SoapAttachment;
class PartFactory
{
public static function createFromSoapAttachment(SoapAttachment $attachment)
{
return new Part(
$attachment->getContent(),
Part::CONTENT_TYPE_PDF,
Part::CHARSET_UTF8,
Part::ENCODING_BINARY,
$attachment->getId()
);
}
/**
* @param SoapAttachment[] $attachments SOAP attachments
* @return Part[]
*/
public static function createAttachmentParts(array $attachments = [])
{
$parts = [];
foreach ($attachments as $attachment) {
$parts[] = self::createFromSoapAttachment($attachment);
}
return $parts;
}
}

View File

@ -8,7 +8,7 @@ namespace BeSimple\SoapCommon;
* the chain-of-responsibility pattern). * the chain-of-responsibility pattern).
* *
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapKernel class SoapKernel
{ {
@ -20,7 +20,7 @@ class SoapKernel
* @param int $attachmentType = SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA|SoapOptions::ATTACHMENTS_TYPE_MTOM|SoapOptions::ATTACHMENTS_TYPE_BASE64 * @param int $attachmentType = SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA|SoapOptions::ATTACHMENTS_TYPE_MTOM|SoapOptions::ATTACHMENTS_TYPE_BASE64
* @return SoapRequest * @return SoapRequest
*/ */
public function filterRequest(SoapRequest $request, array $filters, $attachmentType) public static function filterRequest(SoapRequest $request, array $filters, $attachmentType)
{ {
foreach ($filters as $filter) { foreach ($filters as $filter) {
if ($filter instanceof SoapRequestFilter) { if ($filter instanceof SoapRequestFilter) {
@ -37,9 +37,9 @@ class SoapKernel
* @param SoapResponse $response SOAP response * @param SoapResponse $response SOAP response
* @param SoapRequestFilter[]|SoapResponseFilter[] $filters * @param SoapRequestFilter[]|SoapResponseFilter[] $filters
* @param int $attachmentType = SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA|SoapOptions::ATTACHMENTS_TYPE_MTOM|SoapOptions::ATTACHMENTS_TYPE_BASE64 * @param int $attachmentType = SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA|SoapOptions::ATTACHMENTS_TYPE_MTOM|SoapOptions::ATTACHMENTS_TYPE_BASE64
* @return SoapResponse * @return \BeSimple\SoapClient\SoapResponse|\BeSimple\SoapServer\SoapResponse
*/ */
public function filterResponse(SoapResponse $response, array $filters, $attachmentType) public static function filterResponse(SoapResponse $response, array $filters, $attachmentType)
{ {
foreach ($filters as $filter) { foreach ($filters as $filter) {
if ($filter instanceof SoapResponseFilter) { if ($filter instanceof SoapResponseFilter) {

View File

@ -62,7 +62,7 @@ abstract class SoapMessage
/** /**
* Mime attachments. * Mime attachments.
* *
* @var array(\BeSimple\SoapCommon\Mime\Part) * @var \BeSimple\SoapCommon\Mime\Part[]
*/ */
protected $attachments; protected $attachments;

View File

@ -66,6 +66,7 @@ class SoapOptions
$this->soapFeatures = $features; $this->soapFeatures = $features;
$this->wsdlFile = $wsdlFile; $this->wsdlFile = $wsdlFile;
$this->wsdlCacheType = $wsdlCacheType; $this->wsdlCacheType = $wsdlCacheType;
$this->wsdlCacheDir = $wsdlCacheDir;
$this->classMap = $classMap; $this->classMap = $classMap;
$this->typeConverterCollection = $typeConverterCollection; $this->typeConverterCollection = $typeConverterCollection;
$this->attachmentType = $attachmentType; $this->attachmentType = $attachmentType;

View File

@ -18,11 +18,11 @@ use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use InvalidArgumentException; use InvalidArgumentException;
/** /**
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapOptionsBuilder class SoapOptionsBuilder
{ {
static public function createWithDefaults( public static function createWithDefaults(
$wsdlFile, $wsdlFile,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE, $wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$wsdlCacheDir = null $wsdlCacheDir = null
@ -30,16 +30,37 @@ class SoapOptionsBuilder
return self::createWithClassMap($wsdlFile, new ClassMap(), $wsdlCacheType, $wsdlCacheDir); return self::createWithClassMap($wsdlFile, new ClassMap(), $wsdlCacheType, $wsdlCacheDir);
} }
static public function createSwaWithClassMap( public static function createSwaWithClassMap(
$wsdlFile, $wsdlFile,
ClassMap $classMap, ClassMap $classMap,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE, $wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$wsdlCacheDir = null $wsdlCacheDir = null
) { ) {
return self::createWithClassMap($wsdlFile, $classMap, $wsdlCacheType, $wsdlCacheDir, SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA); return self::createWithClassMap(
$wsdlFile,
$classMap,
$wsdlCacheType,
$wsdlCacheDir,
SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA
);
} }
static public function createWithClassMap( public static function createSwaWithClassMapV11(
$wsdlFile,
ClassMap $classMap,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$wsdlCacheDir = null
) {
return self::createWithClassMapV11(
$wsdlFile,
$classMap,
$wsdlCacheType,
$wsdlCacheDir,
SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA
);
}
public static function createWithClassMap(
$wsdlFile, $wsdlFile,
ClassMap $classMap, ClassMap $classMap,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE, $wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
@ -54,7 +75,8 @@ class SoapOptionsBuilder
throw new InvalidArgumentException('Cache dir must be set for this wsdl cache type'); throw new InvalidArgumentException('Cache dir must be set for this wsdl cache type');
} }
} }
$soapOptions = new SoapOptions(
return new SoapOptions(
SoapOptions::SOAP_VERSION_1_2, SoapOptions::SOAP_VERSION_1_2,
SoapOptions::SOAP_ENCODING_UTF8, SoapOptions::SOAP_ENCODING_UTF8,
SoapOptions::SOAP_CONNECTION_KEEP_ALIVE_OFF, SoapOptions::SOAP_CONNECTION_KEEP_ALIVE_OFF,
@ -68,7 +90,37 @@ class SoapOptionsBuilder
new TypeConverterCollection(), new TypeConverterCollection(),
$attachmentType $attachmentType
); );
}
return $soapOptions; public static function createWithClassMapV11(
$wsdlFile,
ClassMap $classMap,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$wsdlCacheDir = null,
$attachmentType = null
) {
if (!Cache::hasType($wsdlCacheType)) {
throw new InvalidArgumentException('Invalid cache type');
}
if ($wsdlCacheType !== SoapOptions::SOAP_CACHE_TYPE_NONE) {
if ($wsdlCacheDir === null) {
throw new InvalidArgumentException('Cache dir must be set for this wsdl cache type');
}
}
return new SoapOptions(
SoapOptions::SOAP_VERSION_1_1,
SoapOptions::SOAP_ENCODING_UTF8,
SoapOptions::SOAP_CONNECTION_KEEP_ALIVE_OFF,
new SoapFeatures([
SoapFeatures::SINGLE_ELEMENT_ARRAYS
]),
$wsdlFile,
$wsdlCacheType,
$wsdlCacheDir,
$classMap,
new TypeConverterCollection(),
$attachmentType
);
} }
} }

View File

@ -7,19 +7,22 @@ class SoapRequestFactory
/** /**
* Factory function for SoapRequest. * Factory function for SoapRequest.
* *
* @param string $location Location * @param string $location Location
* @param string $action SOAP action * @param string $action SOAP action
* @param string $version SOAP version * @param string $version SOAP version
* @param string $contentType Content Type * @param string $contentType Content Type
* @param string $content Content * @param string $content Content
*
* @return SoapRequest * @return SoapRequest
*/ */
public static function create($location, $action, $version, $contentType, $content = null) public static function createWithContentType(
{ $location,
$action,
$version,
$contentType,
$content = null
) {
$request = new SoapRequest(); $request = new SoapRequest();
// $content is if unmodified from SoapClient not a php string type! $request->setContent($content);
$request->setContent((string) $content);
$request->setLocation($location); $request->setLocation($location);
$request->setAction($action); $request->setAction($action);
$request->setVersion($version); $request->setVersion($version);
@ -27,4 +30,29 @@ class SoapRequestFactory
return $request; return $request;
} }
/**
* Factory function for SoapRequest.
*
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
* @param string $content Content
* @return SoapRequest
*/
public static function create(
$location,
$action,
$version,
$content = null
) {
return self::createWithContentType(
$location,
$action,
$version,
SoapRequest::getContentTypeForVersion($version),
$content
);
}
} }

View File

@ -13,14 +13,31 @@
namespace BeSimple\SoapCommon; namespace BeSimple\SoapCommon;
use BeSimple\SoapCommon\SoapMessage; use BeSimple\SoapClient\SoapResponseTracingData;
/** /**
* SOAP response message. * SOAP response message.
* *
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapResponse extends SoapMessage class SoapResponse extends SoapMessage
{ {
/** @var SoapResponseTracingData */
protected $tracingData;
} public function hasTracingData()
{
return $this->tracingData !== null;
}
public function getTracingData()
{
return $this->tracingData;
}
public function setTracingData(SoapResponseTracingData $tracingData)
{
$this->tracingData = $tracingData;
}
}

View File

@ -23,43 +23,43 @@ class ClassMapTest extends \PHPUnit_Framework_TestCase
{ {
public function testAll() public function testAll()
{ {
$classmap = new ClassMap(); $classMap = new ClassMap();
$this->assertSame(array(), $classmap->getAll()); $this->assertSame([], $classMap->getAll());
} }
public function testAdd() public function testAdd()
{ {
$classmap = new ClassMap(); $classMap = new ClassMap();
$classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap'); $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
$this->setExpectedException('InvalidArgumentException'); $this->setExpectedException('InvalidArgumentException');
$classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap'); $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
} }
public function testGet() public function testGet()
{ {
$classmap = new ClassMap(); $classMap = new ClassMap();
$classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap'); $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
$this->assertSame('BeSimple\SoapCommon\ClassMap', $classmap->get('foobar')); $this->assertSame('BeSimple\SoapCommon\ClassMap', $classMap->get('foobar'));
$this->setExpectedException('InvalidArgumentException'); $this->setExpectedException('Exception');
$classmap->get('bar'); $classMap->get('bar');
} }
public function testAddClassMap() public function testAddClassMap()
{ {
$classmap1 = new ClassMap(); $classMap1 = new ClassMap();
$classmap2 = new ClassMap(); $classMap2 = new ClassMap();
$classmap2->add('foobar', 'BeSimple\SoapCommon\ClassMap'); $classMap2->add('foobar', 'BeSimple\SoapCommon\ClassMap');
$classmap1->addClassMap($classmap2); $classMap1->addClassMap($classMap2);
$this->assertEquals(array('foobar' => 'BeSimple\SoapCommon\ClassMap'), $classmap1->getAll()); $this->assertEquals(['foobar' => 'BeSimple\SoapCommon\ClassMap'], $classMap1->getAll());
$this->setExpectedException('InvalidArgumentException'); $this->setExpectedException('Exception');
$classmap1->addClassMap($classmap2); $classMap1->addClassMap($classMap2);
} }
} }

View File

@ -19,7 +19,7 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
use BeSimple\SoapCommon\Mime\Part; use BeSimple\SoapCommon\Mime\Part;
use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter; use BeSimple\SoapCommon\SoapRequestFilter;
use BeSimple\SoapCommon\SoapResponse; use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter; use BeSimple\SoapCommon\SoapResponseFilter;
/** /**
@ -47,7 +47,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
return $request; return $request;
} }
public function filterResponse(SoapResponse $response, $attachmentType) public function filterResponse(CommonSoapResponse $response, $attachmentType)
{ {
$attachmentsToSend = $response->getAttachments(); $attachmentsToSend = $response->getAttachments();
if (count($attachmentsToSend) > 0) { if (count($attachmentsToSend) > 0) {

View File

@ -13,24 +13,30 @@
namespace BeSimple\SoapServer; namespace BeSimple\SoapServer;
use BeSimple\SoapBundle\Soap\SoapAttachment; use BeSimple\SoapBundle\Soap\SoapAttachment;
use BeSimple\SoapCommon\Mime\Part; use BeSimple\SoapClient\SoapResponseTracingData;
use BeSimple\SoapCommon\Mime\PartFactory;
use BeSimple\SoapCommon\SoapMessage; use BeSimple\SoapCommon\SoapMessage;
class SoapResponseFactory class SoapResponseFactory
{ {
/** /**
* Factory function for SoapResponse. * Factory function for SoapServer\SoapResponse.
* *
* @param string $content Content * @param string $content Content
* @param string $location Location * @param string $location Location
* @param string $action SOAP action * @param string $action SOAP action
* @param string $version SOAP version * @param string $version SOAP version
* @param SoapAttachment[] $attachments SOAP attachments * @param SoapAttachment[] $attachments SOAP attachments
* *
* @return SoapResponse * @return SoapResponse
*/ */
public static function create($content, $location, $action, $version, $attachments = []) public static function create(
{ $content,
$location,
$action,
$version,
array $attachments = []
) {
$response = new SoapResponse(); $response = new SoapResponse();
$response->setContent($content); $response->setContent($content);
$response->setLocation($location); $response->setLocation($location);
@ -38,10 +44,9 @@ class SoapResponseFactory
$response->setVersion($version); $response->setVersion($version);
$contentType = SoapMessage::getContentTypeForVersion($version); $contentType = SoapMessage::getContentTypeForVersion($version);
$response->setContentType($contentType); $response->setContentType($contentType);
if (count($attachments) > 0) { if (count($attachments) > 0) {
$response->setAttachments( $response->setAttachments(
self::createAttachmentParts($attachments) PartFactory::createAttachmentParts($attachments)
); );
} }
@ -49,23 +54,39 @@ class SoapResponseFactory
} }
/** /**
* @param SoapAttachment[] $attachments SOAP attachments * Factory function for SoapServer\SoapResponse.
* @return Part[] *
* @param string $content Content
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
* @param SoapResponseTracingData $tracingData Data value object suitable for tracing SOAP traffic
* @param SoapAttachment[] $attachments SOAP attachments
*
* @return SoapResponse
*/ */
private static function createAttachmentParts(array $attachments = []) public static function createWithTracingData(
{ $content,
$parts = []; $location,
foreach ($attachments as $attachment) { $action,
$part = new Part( $version,
$attachment->getContent(), SoapResponseTracingData $tracingData,
'application/pdf', array $attachments = []
'utf-8', ) {
Part::ENCODING_BINARY, $response = new SoapResponse();
$attachment->getId() $response->setContent($content);
$response->setLocation($location);
$response->setAction($action);
$response->setVersion($version);
$response->setTracingData($tracingData);
$contentType = SoapMessage::getContentTypeForVersion($version);
$response->setContentType($contentType);
if (count($attachments) > 0) {
$response->setAttachments(
PartFactory::createAttachmentParts($attachments)
); );
$parts[] = $part;
} }
return $parts; return $response;
} }
} }

View File

@ -30,7 +30,7 @@ use InvalidArgumentException;
* *
* @author Andreas Schamberger <mail@andreass.net> * @author Andreas Schamberger <mail@andreass.net>
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapServer extends \SoapServer class SoapServer extends \SoapServer
{ {
@ -85,16 +85,15 @@ class SoapServer extends \SoapServer
*/ */
public function createRequest($requestUrl, $soapAction, $requestContentType, $requestContent = null) public function createRequest($requestUrl, $soapAction, $requestContentType, $requestContent = null)
{ {
$soapRequest = SoapRequestFactory::create( $soapRequest = SoapRequestFactory::createWithContentType(
$requestUrl, $requestUrl,
$soapAction, $soapAction,
$this->soapVersion, $this->soapVersion,
$requestContentType, $requestContentType,
$requestContent $requestContent
); );
$soapKernel = new SoapKernel();
if ($this->soapOptions->hasAttachments()) { if ($this->soapOptions->hasAttachments()) {
$soapRequest = $soapKernel->filterRequest( $soapRequest = SoapKernel::filterRequest(
$soapRequest, $soapRequest,
$this->getAttachmentFilters(), $this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType() $this->soapOptions->getAttachmentType()
@ -180,7 +179,7 @@ class SoapServer extends \SoapServer
* @param SoapAttachment[] $attachments * @param SoapAttachment[] $attachments
* @return SoapResponse * @return SoapResponse
*/ */
private function createResponse($requestLocation, $soapAction, $soapVersion, $responseContent = null, $attachments = []) private function createResponse($requestLocation, $soapAction, $soapVersion, $responseContent = null, array $attachments = [])
{ {
$soapResponse = SoapResponseFactory::create( $soapResponse = SoapResponseFactory::create(
$responseContent, $responseContent,
@ -189,9 +188,8 @@ class SoapServer extends \SoapServer
$soapVersion, $soapVersion,
$attachments $attachments
); );
$soapKernel = new SoapKernel();
if ($this->soapOptions->hasAttachments()) { if ($this->soapOptions->hasAttachments()) {
$soapResponse = $soapKernel->filterResponse( $soapResponse = SoapKernel::filterResponse(
$soapResponse, $soapResponse,
$this->getAttachmentFilters(), $this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType() $this->soapOptions->getAttachmentType()

View File

@ -18,7 +18,7 @@ use BeSimple\SoapServer\SoapOptions\SoapServerOptions;
/** /**
* SoapServerBuilder provides a SoapServer instance from SoapServerOptions and SoapOptions. * SoapServerBuilder provides a SoapServer instance from SoapServerOptions and SoapOptions.
* *
* @author Petr Bechyně <petr.bechyne@vodafone.com> * @author Petr Bechyně <mail@petrbechyne.com>
*/ */
class SoapServerBuilder class SoapServerBuilder
{ {

View File

@ -0,0 +1,35 @@
<?php
namespace BeSimple\SoapServer\Tests\Attachment;
class Attachment
{
/**
* @var string $fileName
*/
public $fileName;
/**
* @var string $content
*/
public $contentType;
/**
* @var string $content
*/
public $content;
/**
* Attachment constructor.
*
* @param string $fileName
* @param string $contentType
* @param string $content
*/
public function __construct($fileName, $contentType, $content)
{
$this->fileName = $fileName;
$this->contentType = $contentType;
$this->content = $content;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace BeSimple\SoapServer\Tests\Attachment;
class AttachmentCollection
{
/**
* @var Attachment[] $attachments
*/
public $attachments;
public function __construct(array $attachments = null)
{
$this->attachments = $attachments;
}
public function hasAttachments()
{
return $this->attachments !== null && count($this->attachments) > 0;
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace BeSimple\SoapServer\Tests;
use BeSimple\SoapBundle\Soap\SoapAttachment;
use BeSimple\SoapCommon\AttachmentsHandlerInterface;
use BeSimple\SoapCommon\Storage\RequestHandlerAttachmentsStorage;
use BeSimple\SoapServer\Tests\Attachment\Attachment;
use BeSimple\SoapServer\Tests\Attachment\AttachmentCollection;
use ReflectionClass;
class DummyService implements AttachmentsHandlerInterface
{
/** @var RequestHandlerAttachmentsStorage */
private $requestHandlerAttachmentsStorage;
public function addAttachmentStorage(RequestHandlerAttachmentsStorage $requestHandlerAttachmentsStorage)
{
$this->requestHandlerAttachmentsStorage = $requestHandlerAttachmentsStorage;
}
public function getAttachmentStorage()
{
return $this->requestHandlerAttachmentsStorage;
}
/**
* @return string[]
*/
public function getClassMap()
{
return [
'DummyServiceResponse' => DummyServiceResponse::class,
'DummyServiceResponseWithAttachments' => DummyServiceResponseWithAttachments::class,
'DummyServiceRequest' => DummyServiceRequest::class,
'DummyServiceRequestWithAttachments' => DummyServiceRequestWithAttachments::class,
];
}
/**
* @exclude
* @return string
*/
public function getWsdlPath()
{
$class = new ReflectionClass(static::class);
return __DIR__.DIRECTORY_SEPARATOR.$class->getShortName().'.wsdl';
}
/**
* @return string
*/
public function getEndpoint()
{
return 'http://my.test/soap/dummyService';
}
/**
* @param DummyServiceRequest $dummyServiceRequest
* @return DummyServiceResponse
*/
public function dummyServiceMethod(DummyServiceRequest $dummyServiceRequest)
{
$dummyServiceHandler = new DummyServiceHandler();
return $dummyServiceHandler->handle($dummyServiceRequest);
}
/**
* @param DummyServiceRequestWithAttachments $dummyServiceRequestWithAttachments
* @return DummyServiceResponseWithAttachments
*/
public function dummyServiceMethodWithAttachments(DummyServiceRequestWithAttachments $dummyServiceRequestWithAttachments)
{
if ($dummyServiceRequestWithAttachments->hasAttachments() === true) {
$attachmentStorage = $this->getAttachmentStorage();
$attachments = [];
foreach ($attachmentStorage->getAttachments() as $soapAttachment) {
$attachments[] = new Attachment(
$soapAttachment->getId(),
$soapAttachment->getType(),
$soapAttachment->getContent()
);
}
$dummyServiceRequestWithAttachments->attachmentCollection = new AttachmentCollection($attachments);
}
$dummyServiceHandlerWithAttachments = new DummyServiceHandlerWithAttachments();
$dummyServiceResponseWithAttachments = $dummyServiceHandlerWithAttachments->handle($dummyServiceRequestWithAttachments);
if ($dummyServiceResponseWithAttachments->hasAttachments() === true) {
$soapAttachments = [];
foreach ($dummyServiceResponseWithAttachments->attachmentCollection->attachments as $attachment) {
$soapAttachments[] = new SoapAttachment(
$attachment->fileName,
$attachment->contentType,
$attachment->content
);
}
$this->addAttachmentStorage(new RequestHandlerAttachmentsStorage($soapAttachments));
}
return $dummyServiceResponseWithAttachments;
}
}

View File

@ -0,0 +1,92 @@
<?xml version="1.0"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://schema.testcase" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://schema.testcase">
<wsdl:types>
<xsd:schema targetNamespace="http://schema.testcase">
<xsd:complexType name="SoapHeaderEntity">
<xsd:sequence>
<xsd:element name="user" type="xsd:string" minOccurs="1">
<xsd:annotation>
<xsd:documentation>User name for authorization</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="SoapHeader" type="tns:SoapHeaderEntity"/>
<xsd:complexType name="DummyServiceRequest">
<xsd:sequence>
<xsd:element name="dummyAttribute" type="xsd:int" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DummyServiceResponse">
<xsd:sequence>
<xsd:element name="status" type="xsd:boolean" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DummyServiceRequestWithAttachments">
<xsd:sequence>
<xsd:element name="dummyAttribute" type="xsd:int" minOccurs="1"/>
<xsd:element name="includeAttachments" type="xsd:boolean" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DummyServiceResponseWithAttachments">
<xsd:sequence>
<xsd:element name="status" type="xsd:boolean" minOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<message name="SoapHeader">
<part name="SoapHeader" element="tns:SoapHeader"/>
</message>
<message name="DummyServiceRequest">
<part name="request" type="tns:DummyServiceRequest"/>
</message>
<message name="DummyServiceRequestWithAttachments">
<part name="request" type="tns:DummyServiceRequestWithAttachments"/>
</message>
<message name="DummyServiceResponse">
<part name="dummyServiceReturn" type="tns:DummyServiceResponse"/>
</message>
<message name="DummyServiceResponseWithAttachments">
<part name="dummyServiceReturn" type="tns:DummyServiceResponseWithAttachments"/>
</message>
<wsdl:portType name="DummyServiceSoapPortType">
<wsdl:operation name="dummyServiceMethod">
<wsdl:input message="tns:DummyServiceRequest"/>
<wsdl:output message="tns:DummyServiceResponse"/>
</wsdl:operation>
<wsdl:operation name="dummyServiceMethodWithAttachments">
<wsdl:input message="tns:DummyServiceRequestWithAttachments"/>
<wsdl:output message="tns:DummyServiceResponseWithAttachments"/>
</wsdl:operation>
</wsdl:portType>
<binding name="DummyServiceSoapBinding" type="tns:DummyServiceSoapPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="dummyServiceMethod">
<soap:operation soapAction="DummyService.dummyServiceMethod" style="rpc"/>
<wsdl:input>
<soap:header use="literal" message="tns:SoapHeader" part="SoapHeader" namespace="http://schema.testcase"/>
<soap:body use="literal" part="request" namespace="http://schema.testcase"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal" namespace="http://schema.testcase"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="dummyServiceMethodWithAttachments">
<soap:operation soapAction="DummyService.dummyServiceMethodWithAttachments" style="rpc"/>
<wsdl:input>
<soap:header use="literal" message="tns:SoapHeader" part="SoapHeader" namespace="http://schema.testcase"/>
<soap:body use="literal" part="request" namespace="http://schema.testcase"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal" namespace="http://schema.testcase"/>
</wsdl:output>
</wsdl:operation>
</binding>
<wsdl:service name="DummyService">
<xsd:documentation>WSDL file for DummyService</xsd:documentation>
<port name="DummyServiceSoapPortType" binding="tns:DummyServiceSoapBinding">
<soap:address location="http://schema.testcase"/>
</port>
</wsdl:service>
</wsdl:definitions>

View File

@ -0,0 +1,18 @@
<?php
namespace BeSimple\SoapServer\Tests;
class DummyServiceHandler
{
/**
* @param DummyServiceRequest
* @return DummyServiceResponse
*/
public function handle(DummyServiceRequest $request)
{
$response = new DummyServiceResponse();
$response->status = true;
return $response;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace BeSimple\SoapServer\Tests;
use BeSimple\SoapServer\Tests\Attachment\Attachment;
use BeSimple\SoapServer\Tests\Attachment\AttachmentCollection;
class DummyServiceHandlerWithAttachments
{
/**
* @param DummyServiceRequestWithAttachments $request
* @return DummyServiceResponseWithAttachments
*/
public function handle(DummyServiceRequestWithAttachments $request)
{
$response = new DummyServiceResponseWithAttachments();
$response->status = true;
if ($request->includeAttachments === true) {
if ($request->hasAttachments() === true) {
$attachments = [];
foreach ($request->attachmentCollection->attachments as $attachment) {
$attachments[] = new Attachment($attachment->fileName, $attachment->contentType, $attachment->content);
}
$response->attachmentCollection = new AttachmentCollection($attachments);
}
}
return $response;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace BeSimple\SoapServer\Tests;
class DummyServiceRequest
{
/**
* @var int $dummyAttribute
*/
public $dummyAttribute;
}

View File

@ -0,0 +1,28 @@
<?php
namespace BeSimple\SoapServer\Tests;
use BeSimple\SoapServer\Tests\Attachment\AttachmentCollection;
class DummyServiceRequestWithAttachments
{
/**
* @var int $dummyAttribute
*/
public $dummyAttribute;
/**
* @var bool $includeAttachments
*/
public $includeAttachments;
/**
* @var AttachmentCollection $attachmentCollection
*/
public $attachmentCollection;
public function hasAttachments()
{
return $this->attachmentCollection !== null && $this->attachmentCollection->hasAttachments();
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace BeSimple\SoapServer\Tests;
class DummyServiceResponse
{
/**
* @var bool $status
*/
public $status;
}

View File

@ -0,0 +1,23 @@
<?php
namespace BeSimple\SoapServer\Tests;
use BeSimple\SoapServer\Tests\Attachment\AttachmentCollection;
class DummyServiceResponseWithAttachments
{
/**
* @var bool $status
*/
public $status;
/**
* @var AttachmentCollection $attachmentCollection
*/
public $attachmentCollection;
public function hasAttachments()
{
return $this->attachmentCollection !== null && $this->attachmentCollection->hasAttachments();
}
}

View File

@ -12,30 +12,139 @@
namespace BeSimple\SoapServer\Tests; namespace BeSimple\SoapServer\Tests;
use BeSimple\SoapClient\Tests\SoapClientBuilderTest;
use BeSimple\SoapCommon\ClassMap;
use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use BeSimple\SoapCommon\SoapOptionsBuilder;
use BeSimple\SoapServer\SoapOptions\SoapServerOptions;
use BeSimple\SoapServer\SoapServerBuilder; use BeSimple\SoapServer\SoapServerBuilder;
use BeSimple\SoapServer\SoapServerOptionsBuilder;
/** /**
* UnitTest for \BeSimple\SoapServer\SoapServerBuilder * UnitTest for \BeSimple\SoapServer\SoapServerBuilder
* *
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
* @author Petr Bechyne <mail@petrbechyne.com>
*/ */
class SoapServerBuilderTest extends \PHPUnit_Framework_TestCase class SoapServerBuilderTest extends \PHPUnit_Framework_TestCase
{ {
public function testUnconfiguredWsdl() const TEST_LOCAL_WSDL_UK = SoapClientBuilderTest::TEST_LOCAL_WSDL_UK;
{ const CACHE_DIR = __DIR__ . '/../../../../cache';
$builder = $this->getSoapServerBuilder();
$this->setExpectedException('InvalidArgumentException'); public function testSoapOptionsCreateWithDefaults()
$builder->build(); {
$defaultOptions = SoapOptionsBuilder::createWithDefaults(self::TEST_LOCAL_WSDL_UK);
self::assertInstanceOf(SoapOptions::class, $defaultOptions);
self::assertEquals(self::TEST_LOCAL_WSDL_UK, $defaultOptions->getWsdlFile());
} }
public function testUnconfiguredHandler() public function testSoapClientOptionsCreateWithDefaults()
{ {
$builder = $this->getSoapServerBuilder(); $defaultOptions = SoapServerOptionsBuilder::createWithDefaults(new SoapServerHandler);
$builder->withWsdl('my.wsdl');
$this->setExpectedException('InvalidArgumentException'); self::assertInstanceOf(SoapServerOptions::class, $defaultOptions);
$builder->build(); self::assertInstanceOf(SoapServerHandler::class, $defaultOptions->getHandlerInstance());
}
public function testSoapServerBuilderBuild()
{
$soapServer = $this->getSoapServerBuilder()->build(
SoapServerOptionsBuilder::createWithDefaults(new SoapServerHandler),
SoapOptionsBuilder::createWithDefaults(self::TEST_LOCAL_WSDL_UK)
);
$soapRequest = $soapServer->createRequest('request-url', 'soap-action', 'content/type', 'request-content');
self::assertEquals('content/type', $soapRequest->getContentType());
self::assertEquals('soap-action', $soapRequest->getAction());
self::assertEquals('request-content', $soapRequest->getContent());
self::assertFalse($soapRequest->hasAttachments());
self::assertNull($soapRequest->getAttachments());
}
public function testHandleRequest()
{
$dummyService = new DummyService();
$classMap = new ClassMap();
foreach ($dummyService->getClassMap() as $type => $className) {
$classMap->add($type, $className);
}
$soapServerBuilder = new SoapServerBuilder();
$soapServerOptions = SoapServerOptionsBuilder::createWithDefaults($dummyService);
$soapOptions = SoapOptionsBuilder::createWithClassMap($dummyService->getWsdlPath(), $classMap);
$soapServer = $soapServerBuilder->build($soapServerOptions, $soapOptions);
$request = $soapServer->createRequest(
$dummyService->getEndpoint(),
'DummyService.dummyServiceMethod',
'text/xml;charset=UTF-8',
file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'testHandleRequest.message')
);
$response = $soapServer->handleRequest($request);
file_put_contents(self::CACHE_DIR . '/SoapServerTestResponse.xml', $response->getContent());
self::assertNotContains("\r\n", $response->getContent(), 'Response cannot contain CRLF line endings');
self::assertContains('dummyServiceMethodResponse', $response->getContent());
self::assertSame('DummyService.dummyServiceMethod', $response->getAction());
self::assertFalse($response->hasAttachments(), 'Response should not contain attachments');
}
public function testHandleRequestWithSwa()
{
$dummyService = new DummyService();
$classMap = new ClassMap();
foreach ($dummyService->getClassMap() as $type => $className) {
$classMap->add($type, $className);
}
$soapServerBuilder = new SoapServerBuilder();
$soapServerOptions = SoapServerOptionsBuilder::createWithDefaults($dummyService);
$soapOptions = SoapOptionsBuilder::createSwaWithClassMap($dummyService->getWsdlPath(), $classMap);
$soapServer = $soapServerBuilder->build($soapServerOptions, $soapOptions);
$request = $soapServer->createRequest(
$dummyService->getEndpoint(),
'DummyService.dummyServiceMethodWithAttachments',
'text/xml;charset=UTF-8',
file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'testHandleRequestWithSwa.message')
);
$response = $soapServer->handleRequest($request);
file_put_contents(self::CACHE_DIR . '/SoapServerTestResponseFromSwaRequestWithNoAttachments.xml', $response->getContent());
self::assertNotContains("\r\n", $response->getContent(), 'Response cannot contain CRLF line endings');
self::assertContains('dummyServiceMethodWithAttachmentsResponse', $response->getContent());
self::assertSame('DummyService.dummyServiceMethodWithAttachments', $response->getAction());
self::assertFalse($response->hasAttachments(), 'Response should contain attachments');
}
public function testHandleRequestWithSwaResponse()
{
$dummyService = new DummyService();
$classMap = new ClassMap();
foreach ($dummyService->getClassMap() as $type => $className) {
$classMap->add($type, $className);
}
$soapServerBuilder = new SoapServerBuilder();
$soapServerOptions = SoapServerOptionsBuilder::createWithDefaults($dummyService);
$soapOptions = SoapOptionsBuilder::createSwaWithClassMap($dummyService->getWsdlPath(), $classMap);
$soapServer = $soapServerBuilder->build($soapServerOptions, $soapOptions);
$request = $soapServer->createRequest(
$dummyService->getEndpoint(),
'DummyService.dummyServiceMethodWithAttachments',
'multipart/related; type="text/xml"; start="<rootpart@soapui.org>"; boundary="----=_Part_6_2094841787.1482231370463"',
file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'testHandleRequestWithSwa.mimepart.message')
);
$response = $soapServer->handleRequest($request);
file_put_contents(self::CACHE_DIR . '/SoapServerTestSwaResponseWithAttachments.xml', $response->getContent());
self::assertNotContains("\r\n", $response->getContent(), 'Response cannot contain CRLF line endings');
self::assertContains('dummyServiceMethodWithAttachmentsResponse', $response->getContent());
self::assertSame('DummyService.dummyServiceMethodWithAttachments', $response->getAction());
self::assertTrue($response->hasAttachments(), 'Response should contain attachments');
self::assertCount(2, $response->getAttachments());
} }
public function getSoapServerBuilder() public function getSoapServerBuilder()

View File

@ -0,0 +1,7 @@
<?php
namespace BeSimple\SoapServer\Tests;
class SoapServerHandler
{
}

View File

@ -0,0 +1,14 @@
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://schema.testcase">
<soapenv:Header>
<sch:SoapHeader>
<user>admin</user>
</sch:SoapHeader>
</soapenv:Header>
<soapenv:Body>
<sch:dummyServiceMethod>
<request>
<dummyAttribute>1</dummyAttribute>
</request>
</sch:dummyServiceMethod>
</soapenv:Body>
</soapenv:Envelope>

View File

@ -0,0 +1,15 @@
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://schema.testcase">
<soapenv:Header>
<sch:SoapHeader>
<user>admin</user>
</sch:SoapHeader>
</soapenv:Header>
<soapenv:Body>
<sch:dummyServiceMethodWithAttachments>
<request>
<dummyAttribute>2</dummyAttribute>
<includeAttachments>false</includeAttachments>
</request>
</sch:dummyServiceMethodWithAttachments>
</soapenv:Body>
</soapenv:Envelope>

View File

@ -0,0 +1,62 @@
------=_Part_6_2094841787.1482231370463
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <rootpart@soapui.org>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://schema.testcase">
<soapenv:Header>
<sch:SoapHeader>
<user>admin</user>
</sch:SoapHeader>
</soapenv:Header>
<soapenv:Body>
<sch:dummyServiceMethodWithAttachments>
<request>
<dummyAttribute>3</dummyAttribute>
<includeAttachments>true</includeAttachments>
</request>
</sch:dummyServiceMethodWithAttachments>
</soapenv:Body>
</soapenv:Envelope>
------=_Part_6_2094841787.1482231370463
Content-Type: text/html; charset=us-ascii; name=test-page.html
Content-Transfer-Encoding: 7bit
Content-ID: <test-page.html>
Content-Disposition: attachment; name="test-page.html"; filename="test-page.html"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Test file page</title>
<style type="text/css">
<!--
h1 {
font-family: Arial, Helvetica, sans-serif;
font-size: 11pt;
}
-->
</style>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
------=_Part_6_2094841787.1482231370463
Content-Type: application/x-sh; name=testscript.sh
Content-Transfer-Encoding: binary
Content-ID: <testscript.sh>
Content-Disposition: attachment; name="testscript.sh"; filename="testscript.sh"
#!/bin/sh
### ====================================================================== ###
## ##
## Test Script ##
## ##
### ====================================================================== ###
------=_Part_6_2094841787.1482231370463--