diff --git a/.gitignore b/.gitignore
index 72bd9c9..48c3ad1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
/vendor/
-composer.lock
phpunit.xml
+.idea/
diff --git a/README.md b/README.md
index e1caac8..d4a0c72 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,36 @@
# 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
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
-The BeSimpleSoapClient is a component that extends the native PHP SoapClient with further features like SwA, MTOM 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).
-
+**Refactored** BeSimpleSoapClient is a component that extends the native PHP SoapClient with further features like SwA and WS-Security.
## BeSimpleSoapServer
-The BeSimpleSoapServer is a component that extends the native PHP SoapServer with further features like SwA, MTOM and WS-Security.
-For further information see the [README](https://github.com/BeSimple/BeSimpleSoap/blob/master/src/BeSimple/SoapServer/README.md).
+**Refactored** BeSimpleSoapServer is a component that extends the native PHP SoapServer with further features like SwA and WS-Security.
+
+## BeSimpleSoapCommon
+
+**Refactored** BeSimpleSoapCommon component contains functionality shared by both the server and client implementations.
## 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
@@ -54,3 +55,70 @@ Now you are ready to install the library:
```sh
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',
+ ''
+);
+$response = $soapServer->handleRequest($request);
+
+var_dump($response); // Contains Response, Attachments
+```
diff --git a/cache/.gitignore b/cache/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/cache/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/composer.json b/composer.json
index 98834c7..4e0bc5d 100644
--- a/composer.json
+++ b/composer.json
@@ -19,16 +19,14 @@
},
{
"name": "Petr Bechyně",
- "email": "petr.bechyne@vodafone.com"
+ "email": "mail@petrbechyne.com"
}
],
"require": {
- "php": ">=5.3.0",
+ "php": ">=5.3.0|>=7.0",
"ext-soap": "*",
"ext-curl": "*",
"ass/xmlsecurity": "~1.0",
- "symfony/framework-bundle": "~2.6|~3.0",
- "symfony/twig-bundle": "~2.6|~3.0",
"zendframework/zend-mime": "2.1.*"
},
"replace": {
@@ -41,8 +39,7 @@
"require-dev": {
"ext-mcrypt": "*",
"mikey179/vfsStream": "~1.0",
- "symfony/filesystem": "~2.3",
- "symfony/process": "~2.3"
+ "phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-0": { "BeSimple\\": "src/" }
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..e88b242
--- /dev/null
+++ b/composer.lock
@@ -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": "*"
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index eaf287b..1cb26e7 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -9,6 +9,7 @@
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
+ stderr="true"
bootstrap="vendor/autoload.php"
>
@@ -18,17 +19,8 @@
- ./src/BeSimple/*/Tests/
+ src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php
+ src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php
-
-
-
- ./src/BeSimple/
-
- ./src/BeSimple/*/Tests
- ./src/BeSimple/*/Resources
-
-
-
diff --git a/src/BeSimple/SoapBundle/Cache.php b/src/BeSimple/SoapBundle/Cache.php
index fe5621f..510b35f 100644
--- a/src/BeSimple/SoapBundle/Cache.php
+++ b/src/BeSimple/SoapBundle/Cache.php
@@ -13,27 +13,45 @@
namespace BeSimple\SoapBundle;
use BeSimple\SoapCommon\Cache as BaseCache;
+use BeSimple\SoapCommon\SoapOptions\SoapOptions;
+use Exception;
/**
* @author Francis Besset
*/
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::setType($type);
- BaseCache::setDirectory($directory);
-
- if (null !== $lifetime) {
- BaseCache::setLifetime($lifetime);
+ BaseCache::setEnabled($isEnabled);
+ BaseCache::setType($soapOptions->getWsdlCacheType());
+ BaseCache::setDirectory($soapOptions->getWsdlCacheDir());
+ } else {
+ BaseCache::setEnabled(BaseCache::DISABLED);
+ BaseCache::setType(SoapOptions::SOAP_CACHE_TYPE_NONE);
+ BaseCache::setDirectory(null);
}
+ }
- if (null !== $limit) {
- BaseCache::setLimit($limit);
+ public function validateSettings(SoapOptions $soapOptions)
+ {
+ 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');
+ }
}
}
}
diff --git a/src/BeSimple/SoapBundle/Soap/SoapAttachmentList.php b/src/BeSimple/SoapBundle/Soap/SoapAttachmentList.php
new file mode 100644
index 0000000..e65503a
--- /dev/null
+++ b/src/BeSimple/SoapBundle/Soap/SoapAttachmentList.php
@@ -0,0 +1,36 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapBundle/WebServiceContext.php b/src/BeSimple/SoapBundle/WebServiceContext.php
index 875659e..20c0dde 100644
--- a/src/BeSimple/SoapBundle/WebServiceContext.php
+++ b/src/BeSimple/SoapBundle/WebServiceContext.php
@@ -13,6 +13,8 @@ namespace BeSimple\SoapBundle;
use BeSimple\SoapBundle\ServiceBinding\ServiceBinder;
use BeSimple\SoapCommon\Converter\TypeConverterCollection;
+use BeSimple\SoapCommon\SoapOptionsBuilder;
+use BeSimple\SoapServer\SoapServerOptionsBuilder;
use BeSimple\SoapWsdl\Dumper\Dumper;
use BeSimple\SoapServer\SoapServerBuilder;
use Symfony\Component\Config\ConfigCache;
@@ -102,15 +104,14 @@ class WebServiceContext
public function getServerBuilder()
{
if (null === $this->serverBuilder) {
- $this->serverBuilder = SoapServerBuilder::createWithDefaults()
- ->withWsdl($this->getWsdlFile())
- ->withClassmap($this->getServiceDefinition()->getTypeRepository()->getClassmap())
- ->withTypeConverters($this->converters)
- ;
-
- if (null !== $this->options['cache_type']) {
- $this->serverBuilder->withWsdlCache($this->options['cache_type']);
- }
+ $soapServerBuilder = new SoapServerBuilder();
+ $this->serverBuilder = $soapServerBuilder->build(
+ SoapServerOptionsBuilder::createWithDefaults(),
+ SoapOptionsBuilder::createWithClassMap(
+ $this->getWsdlFile(),
+ $this->getServiceDefinition()->getTypeRepository()->getClassmap()
+ )
+ );
}
return $this->serverBuilder;
diff --git a/src/BeSimple/SoapClient/Curl.php b/src/BeSimple/SoapClient/Curl.php
deleted file mode 100644
index ef48e95..0000000
--- a/src/BeSimple/SoapClient/Curl.php
+++ /dev/null
@@ -1,336 +0,0 @@
-
- * (c) Francis Besset
- *
- * 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
- */
-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));
- }
-}
diff --git a/src/BeSimple/SoapClient/Curl/Curl.php b/src/BeSimple/SoapClient/Curl/Curl.php
new file mode 100644
index 0000000..ae283a2
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/Curl.php
@@ -0,0 +1,234 @@
+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);
+ }
+}
diff --git a/src/BeSimple/SoapClient/Curl/CurlOptions.php b/src/BeSimple/SoapClient/Curl/CurlOptions.php
new file mode 100644
index 0000000..60ef439
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/CurlOptions.php
@@ -0,0 +1,126 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/Curl/CurlOptionsBuilder.php b/src/BeSimple/SoapClient/Curl/CurlOptionsBuilder.php
new file mode 100644
index 0000000..ec49d17
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/CurlOptionsBuilder.php
@@ -0,0 +1,79 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/Curl/CurlResponse.php b/src/BeSimple/SoapClient/Curl/CurlResponse.php
new file mode 100644
index 0000000..417b873
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/CurlResponse.php
@@ -0,0 +1,100 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationBasicOptions.php b/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationBasicOptions.php
new file mode 100644
index 0000000..9eac23e
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationBasicOptions.php
@@ -0,0 +1,34 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationDigestOptions.php b/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationDigestOptions.php
new file mode 100644
index 0000000..bdf04df
--- /dev/null
+++ b/src/BeSimple/SoapClient/Curl/Http/HttpAuthenticationDigestOptions.php
@@ -0,0 +1,11 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/MimeFilter.php b/src/BeSimple/SoapClient/MimeFilter.php
index 898e48f..609ff3e 100644
--- a/src/BeSimple/SoapClient/MimeFilter.php
+++ b/src/BeSimple/SoapClient/MimeFilter.php
@@ -19,7 +19,7 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
use BeSimple\SoapCommon\Mime\Part;
use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter;
-use BeSimple\SoapCommon\SoapResponse;
+use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter;
/**
@@ -62,7 +62,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
return $request;
}
- public function filterResponse(SoapResponse $response, $attachmentType)
+ public function filterResponse(CommonSoapResponse $response, $attachmentType)
{
$multiPartMessage = MimeParser::parseMimeMessage(
$response->getContent(),
diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php
index 483c8e7..b5bc649 100644
--- a/src/BeSimple/SoapClient/SoapClient.php
+++ b/src/BeSimple/SoapClient/SoapClient.php
@@ -12,10 +12,20 @@
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\SoapOptions\SoapOptions;
use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFactory;
+use Exception;
+use SoapFault;
/**
* 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.
*
* @author Andreas Schamberger
+ * @author Petr Bechyně
*/
class SoapClient extends \SoapClient
{
- protected $soapVersion;
- protected $tracingEnabled;
protected $soapClientOptions;
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)
{
- $this->soapVersion = $soapOptions->getSoapVersion();
- $this->tracingEnabled = $soapClientOptions->getTrace();
$this->soapClientOptions = $soapClientOptions;
$this->soapOptions = $soapOptions;
-
- // @todo: refactor SoapClient: do not use $options as array: refactor Curl
- $this->curl = new Curl($soapClientOptions->toArray());
-
- $wsdlFile = $this->loadWsdl(
- $soapOptions->getWsdlFile(),
- $soapOptions->getWsdlCacheType()
+ $this->curl = new Curl(
+ CurlOptionsBuilder::buildForSoapClient($soapClientOptions)
);
- parent::__construct(
- $wsdlFile,
- $soapClientOptions->toArray() + $soapOptions->toArray()
+ try {
+ @parent::__construct(
+ $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.
- * $oneWay parameter is not used at the moment.
+ * Using __soapCall returns only response string, use soapCall instead.
+ *
+ * @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 $location Location
@@ -102,44 +131,105 @@ class SoapClient extends \SoapClient
*/
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
{
- $soapRequest = $this->createSoapRequest($location, $action, $version, $request);
- $soapResponse = $this->getSoapResponseFromRequest($soapRequest);
+ $soapClientNativeDataTransferObject = new SoapClientNativeDataTransferObject(
+ $request,
+ $location,
+ $action,
+ $version
+ );
+ echo json_encode($soapClientNativeDataTransferObject);
- return $soapResponse->getContent();
- }
-
- 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;
+ return $request;
}
/**
- * Runs the currently registered request filters on the request, performs
- * the HTTP request and runs the response filters.
+ * Custom request method to be able to modify the SOAP messages.
+ * $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
*/
- private function getSoapResponseFromRequest(SoapRequest $soapRequest)
+ public function performSoapRequest($request, $location, $action, $version, array $soapAttachments = [])
{
- $soapResponse = $this->performHttpSoapRequest($soapRequest);
- if ($this->soapOptions->hasAttachments()) {
- $soapKernel = new SoapKernel();
- $soapKernel->filterResponse($soapResponse, $this->getAttachmentFilters(), $this->soapOptions->getAttachmentType());
+ $soapRequest = $this->createSoapRequest($location, $action, $version, $request, $soapAttachments);
+
+ return $this->performHttpSoapRequest($soapRequest);
+ }
+
+ /** @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
* @return SoapResponse
+ * @throws SoapFault
*/
private function performHttpSoapRequest(SoapRequest $soapRequest)
{
- // HTTP headers
- $soapVersion = $soapRequest->getVersion();
- $soapAction = $soapRequest->getAction();
- if (SOAP_1_1 === $soapVersion) {
+ if ($soapRequest->getVersion() === SOAP_1_1) {
$headers = [
'Content-Type:' . $soapRequest->getContentType(),
- 'SOAPAction: "' . $soapAction . '"',
+ 'SOAPAction: "' . $soapRequest->getAction() . '"',
];
} else {
$headers = [
- 'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapAction . '"',
+ 'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapRequest->getAction() . '"',
];
}
-
- $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(),
+ $curlResponse = $this->curl->executeCurlWithCachedSession(
$soapRequest->getLocation(),
- $soapRequest->getAction(),
- $soapRequest->getVersion(),
- $this->curl->getResponseContentType()
+ $soapRequest->getContent(),
+ $headers
+ );
+ $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 SoapRequest $soapRequest SOAP request object
- * @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 Curl $curl
+ * @param string $wsdlPath
+ * @param int $wsdlCacheType
* @param bool $resolveRemoteIncludes
*
* @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 {
- $cacheFileName = $wsdlDownloader->download($wsdl);
- } catch (\RuntimeException $e) {
- throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "'");
+ $loadedWsdlFilePath = $wsdlDownloader->getWsdlPath($curl, $wsdlPath, $wsdlCacheType, $resolveRemoteIncludes);
+ } catch (Exception $e) {
+ throw new SoapFault(
+ SoapFaultEnum::SOAP_FAULT_WSDL,
+ 'Unable to load WsdlPath: ' . $e->getMessage()
+ );
}
- return $cacheFileName;
+ return $loadedWsdlFilePath;
}
private function getAttachmentFilters()
@@ -302,4 +337,74 @@ class SoapClient extends \SoapClient
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');
+ }
+ }
}
diff --git a/src/BeSimple/SoapClient/SoapClientBuilder.php b/src/BeSimple/SoapClient/SoapClientBuilder.php
index 00a406f..02e4b0a 100644
--- a/src/BeSimple/SoapClient/SoapClientBuilder.php
+++ b/src/BeSimple/SoapClient/SoapClientBuilder.php
@@ -12,22 +12,44 @@
namespace BeSimple\SoapClient;
+use BeSimple\SoapBundle\Cache;
+use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
use BeSimple\SoapCommon\SoapOptions\SoapOptions;
+use Exception;
+use SoapHeader;
/**
* Provides a SoapClient instance.
*
* @author Francis Besset
* @author Christian Kerl
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapClientBuilder
{
public function build(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions)
{
+ $cache = new Cache($soapOptions);
+ $cache->validateSettings($soapOptions);
+
return new SoapClient(
$soapClientOptions,
$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;
+ }
}
diff --git a/src/BeSimple/SoapClient/SoapClientMessageWithAttachments.php b/src/BeSimple/SoapClient/SoapClientMessageWithAttachments.php
new file mode 100644
index 0000000..bffe5c4
--- /dev/null
+++ b/src/BeSimple/SoapClient/SoapClientMessageWithAttachments.php
@@ -0,0 +1,7 @@
+request = $request;
+ $this->location = $location;
+ $this->action = $action;
+ $this->version = $version;
+ }
+}
diff --git a/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php b/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php
index ef22a2c..aa21b45 100644
--- a/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php
+++ b/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php
@@ -12,12 +12,16 @@
namespace BeSimple\SoapClient;
+use BeSimple\SoapClient\Curl\CurlOptions;
+use BeSimple\SoapClient\SoapOptions\SoapClientOptions;
+use BeSimple\SoapClient\SoapServerAuthentication\SoapServerAuthenticationInterface;
+
/**
* Provides a SoapClient instance.
*
* @author Francis Besset
* @author Christian Kerl
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapClientOptionsBuilder
{
@@ -26,8 +30,29 @@ class SoapClientOptionsBuilder
return new SoapClientOptions(
SoapClientOptions::SOAP_CLIENT_TRACE_OFF,
SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON,
- 'BeSimpleSoap',
+ CurlOptions::DEFAULT_USER_AGENT,
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
+ );
+ }
}
diff --git a/src/BeSimple/SoapClient/SoapFaultWithTracingData.php b/src/BeSimple/SoapClient/SoapFaultWithTracingData.php
new file mode 100644
index 0000000..d62c762
--- /dev/null
+++ b/src/BeSimple/SoapClient/SoapFaultWithTracingData.php
@@ -0,0 +1,21 @@
+soapResponseTracingData = $soapResponseTracingData;
+ parent::__construct($code, $message);
+ }
+
+ public function getSoapResponseTracingData()
+ {
+ return $this->soapResponseTracingData;
+ }
+}
diff --git a/src/BeSimple/SoapClient/SoapClientOptions.php b/src/BeSimple/SoapClient/SoapOptions/SoapClientOptions.php
similarity index 77%
rename from src/BeSimple/SoapClient/SoapClientOptions.php
rename to src/BeSimple/SoapClient/SoapOptions/SoapClientOptions.php
index 4f3a141..fc1e5db 100644
--- a/src/BeSimple/SoapClient/SoapClientOptions.php
+++ b/src/BeSimple/SoapClient/SoapOptions/SoapClientOptions.php
@@ -1,7 +1,10 @@
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()
{
return $this->proxy !== null;
diff --git a/src/BeSimple/SoapClient/SoapResponse.php b/src/BeSimple/SoapClient/SoapResponse.php
index cafee41..2a0e851 100644
--- a/src/BeSimple/SoapClient/SoapResponse.php
+++ b/src/BeSimple/SoapClient/SoapResponse.php
@@ -1,46 +1,13 @@
- * (c) Francis Besset
- *
- * 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\SoapCommon\SoapResponse as CommonSoapResponse;
-/**
- * SoapResponse class for SoapClient. Provides factory function for response object.
- *
- * @author Andreas Schamberger
- */
class SoapResponse extends CommonSoapResponse
{
- /**
- * 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)
+ public function getResponseContent()
{
- $response = new SoapResponse();
- $response->setContent($content);
- $response->setLocation($location);
- $response->setAction($action);
- $response->setVersion($version);
- $response->setContentType($contentType);
-
- return $response;
+ return $this->getContent();
}
}
diff --git a/src/BeSimple/SoapClient/SoapResponseFactory.php b/src/BeSimple/SoapClient/SoapResponseFactory.php
new file mode 100644
index 0000000..d2caf3d
--- /dev/null
+++ b/src/BeSimple/SoapClient/SoapResponseFactory.php
@@ -0,0 +1,98 @@
+
+ * (c) Francis Besset
+ *
+ * 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
+ * @author Petr Bechyně
+ */
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/SoapResponseTracingData.php b/src/BeSimple/SoapClient/SoapResponseTracingData.php
new file mode 100644
index 0000000..c3f13ac
--- /dev/null
+++ b/src/BeSimple/SoapClient/SoapResponseTracingData.php
@@ -0,0 +1,39 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapClient/Tests/CurlTest.php b/src/BeSimple/SoapClient/Tests/CurlTest.php
index 28b6311..be8a80f 100644
--- a/src/BeSimple/SoapClient/Tests/CurlTest.php
+++ b/src/BeSimple/SoapClient/Tests/CurlTest.php
@@ -12,7 +12,8 @@
namespace BeSimple\SoapClient\Tests;
-use BeSimple\SoapClient\Curl;
+use BeSimple\SoapClient\Curl\Curl;
+use BeSimple\SoapClient\Curl\CurlOptionsBuilder;
/**
* @author Andreas Schamberger
@@ -21,11 +22,12 @@ class CurlTest extends AbstractWebserverTest
{
public function testExec()
{
- $curl = new Curl(array(
- 'proxy_host' => false,
- ));
+ $curlOptions = CurlOptionsBuilder::buildDefault();
+ $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)));
}
@@ -81,43 +83,4 @@ class CurlTest extends AbstractWebserverTest
$curl->exec(sprintf('http://localhost:%d/curl.txt', WEBSERVER_PORT));
$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());
- }
}
diff --git a/src/BeSimple/SoapClient/Tests/GenerateTestRequest.php b/src/BeSimple/SoapClient/Tests/GenerateTestRequest.php
new file mode 100644
index 0000000..8a97bc2
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/GenerateTestRequest.php
@@ -0,0 +1,8 @@
+soapCall('generateTest', [$generateTestRequest]);
+
+You can print the SoapFaultWithTracingData attributes in order to investigate the SoapClient request and request headers.
\ No newline at end of file
diff --git a/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response
new file mode 100644
index 0000000..a96157d
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response
@@ -0,0 +1,18 @@
+
+--Part_13_58a1b01a466a6.58a1b01a466e8
+Content-Type: application/soap+xml; charset=utf-8
+Content-Transfer-Encoding: 8bit
+Content-ID:
+
+
+generateTestReturndummy-attachment.txt
+
+--Part_13_58a1b01a466a6.58a1b01a466e8
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+Content-ID:
+Content-Location: dummy-attachment.txt
+
+Hello world!
+
+--Part_13_58a1b01a466a6.58a1b01a466e8--
diff --git a/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response.headers b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response.headers
new file mode 100644
index 0000000..f73280d
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.example.response.headers
@@ -0,0 +1 @@
+multipart/related; type="application/soap+xml"; charset=utf-8; boundary=Part_13_58a1b01a466a6.58a1b01a466e8; start=""
diff --git a/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.wsdl b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.wsdl
new file mode 100644
index 0000000..045b318
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/Mock/MockSwaService.wsdl
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+ User name for authorization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WSDL file for TestGeneratorService
+
+
+
+
+
diff --git a/src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php b/src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php
index 0fc68aa..c61e7d7 100644
--- a/src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php
+++ b/src/BeSimple/SoapClient/Tests/SoapClientBuilderTest.php
@@ -12,113 +12,286 @@
namespace BeSimple\SoapClient\Tests;
+use BeSimple\SoapBundle\Soap\SoapAttachment;
+use BeSimple\SoapClient\Curl\CurlOptions;
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
{
- private $defaultOptions = array(
- 'features' => 0,
- 'classmap' => array(),
- 'typemap' => array(),
- );
+ const CACHE_DIR = __DIR__ . '/../../../../cache';
+ const TEST_ENDPOINT_UK = 'http://www.webservicex.net/uklocation.asmx';
+ const TEST_REMOTE_WSDL_UK = 'http://www.webservicex.net/uklocation.asmx?WSDL';
+ 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
- ->getSoapBuilder()
- ->getSoapOptions()
- ;
+ $defaultOptions = SoapOptionsBuilder::createWithDefaults(self::TEST_LOCAL_WSDL_UK);
- $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();
- $this->assertEquals($this->mergeOptions(array('trace' => true)), $builder->getSoapOptions());
-
- $builder->withTrace(false);
- $this->assertEquals($this->mergeOptions(array('trace' => false)), $builder->getSoapOptions());
+ self::assertInstanceOf(SoapClientOptions::class, $defaultOptions);
+ self::assertEquals(CurlOptions::DEFAULT_USER_AGENT, $defaultOptions->getUserAgent());
}
- public function testWithExceptions()
+ public function testConstructSoapClientWithDefaults()
{
- $builder = $this->getSoapBuilder();
+ $soapClient = $this->getSoapBuilder()->build(
+ SoapClientOptionsBuilder::createWithDefaults(),
+ SoapOptionsBuilder::createWithDefaults(self::TEST_REMOTE_WSDL_UK)
+ );
- $builder->withExceptions();
- $this->assertEquals($this->mergeOptions(array('exceptions' => true)), $builder->getSoapOptions());
-
- $builder->withExceptions(false);
- $this->assertEquals($this->mergeOptions(array('exceptions' => false)), $builder->getSoapOptions());
+ self::assertInstanceOf(\SoapClient::class, $soapClient);
}
- 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');
- $this->assertEquals($this->mergeOptions(array('user_agent' => 'BeSimpleSoap Test')), $builder->getSoapOptions());
+ $soapClient = $this->getSoapBuilder()->build(
+ 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();
- $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());
+ self::assertInstanceOf(\SoapClient::class, $soapClient);
}
- 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');
- $this->assertEquals($this->mergeOptions(array('authentication' => SOAP_AUTHENTICATION_DIGEST, 'local_cert' => __DIR__.'/Fixtures/cert.pem', 'passphrase' => 'foobar')), $builder->getSoapOptions());
+ $soapClient = $this->getSoapBuilder()->build(
+ SoapClientOptionsBuilder::createWithDefaults(),
+ $soapOptions
+ );
- $builder->withDigestAuthentication(__DIR__.'/Fixtures/cert.pem');
- $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());
+ self::assertInstanceOf(\SoapClient::class, $soapClient);
}
- 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);
- $this->assertEquals($this->mergeOptions(array('proxy_host' => 'localhost', 'proxy_port' => 8080)), $builder->getSoapOptions());
+ self::assertContains('GetUKLocationByCountyResult', $soapResponse->getContent());
+ self::assertContains('', $soapResponse->getContent());
+ self::assertEquals(self::TEST_ENDPOINT_UK, $soapResponse->getLocation());
+ }
- $builder->withProxy('127.0.0.1', 8585, 'foo', 'bar');
- $this->assertEquals($this->mergeOptions(array('proxy_host' => '127.0.0.1', 'proxy_port' => 8585, 'proxy_login' => 'foo', 'proxy_password' => 'bar')), $builder->getSoapOptions());
+ public function testSoapCallWithCacheEndpointDownShouldFail()
+ {
+ $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->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());
+ $this->getSoapBuilder()->build(
+ 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);
- $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());
+ public function testSoapCallEndpointDownShouldFail()
+ {
+ $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 {
- $builder->withProxy('127.0.0.1', 8585, 'foo', 'bar', -100);
-
- $this->fail('An expected exception has not been raised.');
- } catch (\Exception $e) {
- $this->assertInstanceOf('InvalidArgumentException', $e);
+ $soapResponse = $soapClient->soapCall(
+ 'GetUKLocationByCounty',
+ [$getUKLocationByCountyRequest],
+ [
+ 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="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="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('', $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()
@@ -126,8 +299,41 @@ class SoapClientBuilderTest extends \PHPUnit_Framework_TestCase
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');
}
}
diff --git a/src/BeSimple/SoapClient/Tests/localWsdl.wsdl b/src/BeSimple/SoapClient/Tests/localWsdl.wsdl
new file mode 100644
index 0000000..e759048
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/localWsdl.wsdl
@@ -0,0 +1,380 @@
+
+
+ Get UK Postcode,Town,County and Validate UK Address
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Get UK town,Postcode and County by full /partial County
+
+
+
+
+ Get UK town,Postcode and County by full /partial Town
+
+
+
+
+ Get UK town,Postcode and County by Postcode(First Section of Post Code)
+
+
+
+
+ Validate UK address,Use First Section of Poscode for Postcode atribute
+
+
+
+
+
+
+ Get UK town,Postcode and County by full /partial County
+
+
+
+
+ Get UK town,Postcode and County by full /partial Town
+
+
+
+
+ Get UK town,Postcode and County by Postcode(First Section of Post Code)
+
+
+
+
+ Validate UK address,Use First Section of Poscode for Postcode atribute
+
+
+
+
+
+
+ Get UK town,Postcode and County by full /partial County
+
+
+
+
+ Get UK town,Postcode and County by full /partial Town
+
+
+
+
+ Get UK town,Postcode and County by Postcode(First Section of Post Code)
+
+
+
+
+ Validate UK address,Use First Section of Poscode for Postcode atribute
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Get UK Postcode,Town,County and Validate UK Address
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/BeSimple/SoapClient/Tests/soapRequestWithNoAttachments.request b/src/BeSimple/SoapClient/Tests/soapRequestWithNoAttachments.request
new file mode 100644
index 0000000..02ec2a1
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/soapRequestWithNoAttachments.request
@@ -0,0 +1,2 @@
+
+London
diff --git a/src/BeSimple/SoapClient/Tests/soapRequestWithTwoAttachments.request b/src/BeSimple/SoapClient/Tests/soapRequestWithTwoAttachments.request
new file mode 100644
index 0000000..4d83591
--- /dev/null
+++ b/src/BeSimple/SoapClient/Tests/soapRequestWithTwoAttachments.request
@@ -0,0 +1,24 @@
+
+--Part_10_589b2dcf4f7fb.589b2dcf4f804
+Content-Type: application/soap+xml; charset=utf-8
+Content-Transfer-Encoding: 8bit
+Content-ID:
+
+
+London
+
+--Part_10_589b2dcf4f7fb.589b2dcf4f804
+Content-Type: application/pdf; charset=utf-8
+Content-Transfer-Encoding: binary
+Content-ID:
+Content-Location: first-file.txt
+
+hello world
+--Part_10_589b2dcf4f7fb.589b2dcf4f804
+Content-Type: application/pdf; charset=utf-8
+Content-Transfer-Encoding: binary
+Content-ID:
+Content-Location: second-file.txt
+
+hello world
+--Part_10_589b2dcf4f7fb.589b2dcf4f804--
\ No newline at end of file
diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php
index 43253c2..562cb5b 100644
--- a/src/BeSimple/SoapClient/WsdlDownloader.php
+++ b/src/BeSimple/SoapClient/WsdlDownloader.php
@@ -12,8 +12,13 @@
namespace BeSimple\SoapClient;
+use BeSimple\SoapClient\Curl\Curl;
use BeSimple\SoapCommon\Cache;
use BeSimple\SoapCommon\Helper;
+use DOMDocument;
+use DOMElement;
+use DOMXPath;
+use Exception;
/**
* Downloads WSDL files with cURL. Uses the WSDL_CACHE_* constants and the
@@ -25,148 +30,179 @@ use BeSimple\SoapCommon\Helper;
*/
class WsdlDownloader
{
- protected $curl;
- protected $resolveRemoteIncludes = true;
- protected $cacheEnabled;
- protected $cacheDir;
- protected $cacheTtl;
-
/**
* @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
- */
- 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
*/
- 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
- // resolve remote XSD includes
- $isRemoteFile = $this->isRemoteFile($wsdl);
- if ($isRemoteFile || $this->resolveRemoteIncludes) {
- $cacheFilePath = $this->cacheDir.DIRECTORY_SEPARATOR.'wsdl_'.md5($wsdl).'.cache';
-
- if (!$this->cacheEnabled || !file_exists($cacheFilePath) || (filemtime($cacheFilePath) + $this->cacheTtl) < time()) {
- if ($isRemoteFile) {
- // execute request
- $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 ."'");
+ $isRemoteFile = $this->isRemoteFile($wsdlPath);
+ $isCacheEnabled = $wsdCacheType === Cache::TYPE_NONE ? false : Cache::isEnabled();
+ if ($isCacheEnabled === true) {
+ $cacheFilePath = Cache::getDirectory().DIRECTORY_SEPARATOR.'wsdl_'.md5($wsdlPath).'.cache';
+ $isCacheExisting = file_exists($cacheFilePath);
+ if ($isCacheExisting) {
+ $fileModificationTime = filemtime($cacheFilePath);
+ if ($fileModificationTime === false) {
+ throw new Exception('File modification time could not be get for wsdl path: ' . $cacheFilePath);
}
+ $isCacheValid = ($fileModificationTime + Cache::getLifetime()) >= time();
+ } else {
+ $isCacheExisting = $isCacheValid = false;
+ }
+ if ($isCacheExisting === false || $isCacheValid === false) {
+ $this->writeCacheFile($curl, $wsdCacheType, $wsdlPath, $cacheFilePath, $resolveRemoteIncludes, $isRemoteFile);
}
- return $cacheFilePath;
- } elseif (file_exists($wsdl)) {
- return realpath($wsdl);
- }
+ return $this->getLocalWsdlPath($cacheFilePath);
- 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 $file File URL/path
- *
+ * @param string $wsdlPath File URL/path
* @return boolean
*/
- private function isRemoteFile($file)
+ private function isRemoteFile($wsdlPath)
{
- // @parse_url to suppress E_WARNING for invalid urls
- if (false !== $url = @parse_url($file)) {
- if (isset($url['scheme']) && 'http' === substr($url['scheme'], 0, 4)) {
+ $parsedUrlOrFalse = @parse_url($wsdlPath);
+ if ($parsedUrlOrFalse !== false) {
+ if (isset($parsedUrlOrFalse['scheme']) && substr($parsedUrlOrFalse['scheme'], 0, 4) === 'http') {
+
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.
*
- * @param string $xml XML file
- * @param string $cacheFilePath Cache file name
+ * @param Curl $curl
+ * @param int $cacheType
+ * @param string $xmlFileSource XML file contents
* @param boolean $parentFilePath Parent file name
- *
- * @return void
+ * @return DOMDocument
*/
- private function resolveRemoteIncludes($xml, $cacheFilePath, $parentFilePath = null)
+ private function getXmlFileDOMDocument(Curl $curl, $cacheType, $xmlFileSource, $parentFilePath = null)
{
- $doc = new \DOMDocument();
- $doc->loadXML($xml);
+ $document = new DOMDocument('1.0', 'utf-8');
+ if ($document->loadXML($xmlFileSource) === false) {
+ throw new Exception('Could not save downloaded WSDL cache: '.$xmlFileSource);
+ }
- $xpath = new \DOMXPath($doc);
- $xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA);
- $xpath->registerNamespace(Helper::PFX_WSDL, Helper::NS_WSDL);
+ $xpath = new DOMXPath($document);
+ $this->updateXmlDocument($curl, $cacheType, $xpath, Helper::PFX_WSDL, Helper::NS_WSDL, 'location', $parentFilePath);
+ $this->updateXmlDocument($curl, $cacheType, $xpath, Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA, 'schemaLocation', $parentFilePath);
- // WSDL include/import
- $query = './/'.Helper::PFX_WSDL.':include | .//'.Helper::PFX_WSDL.':import';
- $nodes = $xpath->query($query);
+ return $document;
+ }
+
+ 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) {
foreach ($nodes as $node) {
- $location = $node->getAttribute('location');
- if ($this->isRemoteFile($location)) {
- $location = $this->download($location);
- $node->setAttribute('location', $location);
- } elseif (null !== $parentFilePath) {
- $location = $this->resolveRelativePathInUrl($parentFilePath, $location);
- $location = $this->download($location);
- $node->setAttribute('location', $location);
+ /** @var DOMElement $node */
+ $locationPath = $node->getAttribute($locationAttributeName);
+ if ($this->isRemoteFile($locationPath)) {
+ $node->setAttribute(
+ $locationAttributeName,
+ $this->getWsdlPath(
+ $curl,
+ $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 /../
foreach ($parts as $key => $part) {
- if ('..' === $part) {
+ if ($part === '..') {
$keyToDelete = $key - 1;
-
while ($keyToDelete > 0) {
if (isset($parts[$keyToDelete])) {
unset($parts[$keyToDelete]);
diff --git a/src/BeSimple/SoapCommon/Cache.php b/src/BeSimple/SoapCommon/Cache.php
index 1029fa6..39f2e60 100644
--- a/src/BeSimple/SoapCommon/Cache.php
+++ b/src/BeSimple/SoapCommon/Cache.php
@@ -12,6 +12,8 @@
namespace BeSimple\SoapCommon;
+use InvalidArgumentException;
+
/**
* @author Francis Besset
*/
@@ -32,22 +34,22 @@ class Cache
self::TYPE_DISK_MEMORY,
];
- static public function getTypes()
+ public static function getTypes()
{
return self::$types;
}
- static public function hasType($cacheType)
+ public static function hasType($cacheType)
{
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)) {
throw new \InvalidArgumentException();
@@ -56,60 +58,58 @@ class Cache
self::iniSet('soap.wsdl_cache_enabled', $enabled);
}
- static public function getType()
+ public static function getType()
{
return self::iniGet('soap.wsdl_cache');
}
- static public function setType($type)
+ public static function setType($type)
{
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);
}
- static public function getDirectory()
+ public static function getDirectory()
{
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);
}
- static public function getLifetime()
+ public static function getLifetime()
{
return self::iniGet('soap.wsdl_cache_ttl');
}
- static public function setLifetime($lifetime)
+ public static function setLifetime($lifetime)
{
self::iniSet('soap.wsdl_cache_ttl', $lifetime);
}
- static public function getLimit()
+ public static function getLimit()
{
return self::iniGet('soap.wsdl_cache_limit');
}
- static public function setLimit($limit)
+ public static function setLimit($limit)
{
self::iniSet('soap.wsdl_cache_limit', $limit);
}
- static protected function iniGet($key)
+ protected static function iniGet($key)
{
return ini_get($key);
}
- static protected function iniSet($key, $value)
+ protected static function iniSet($key, $value)
{
ini_set($key, $value);
}
diff --git a/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php b/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php
index f3bf76f..23cf184 100644
--- a/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php
+++ b/src/BeSimple/SoapCommon/Converter/MtomTypeConverter.php
@@ -22,11 +22,6 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
*/
class MtomTypeConverter implements TypeConverterInterface
{
- /**
- * @var \BeSimple\SoapCommon\SoapKernel $soapKernel SoapKernel instance
- */
- protected $soapKernel = null;
-
/**
* {@inheritDoc}
*/
diff --git a/src/BeSimple/SoapCommon/Fault/SoapFaultEnum.php b/src/BeSimple/SoapCommon/Fault/SoapFaultEnum.php
new file mode 100644
index 0000000..3cc630f
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Fault/SoapFaultEnum.php
@@ -0,0 +1,10 @@
+mainPartContentId;
+ }
}
\ No newline at end of file
diff --git a/src/BeSimple/SoapCommon/Mime/Parser.php b/src/BeSimple/SoapCommon/Mime/Parser.php
index 48f4f4c..63c949f 100644
--- a/src/BeSimple/SoapCommon/Mime/Parser.php
+++ b/src/BeSimple/SoapCommon/Mime/Parser.php
@@ -12,183 +12,241 @@
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.
*
* @author Andreas Schamberger
+ * @author Petr Bechyne
*/
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.
*
* @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
*/
public static function parseMimeMessage($mimeMessage, array $headers = [])
{
- $boundary = null;
- $start = null;
- $multipart = new MultiPart();
- $hitFirstBoundary = false;
- $inHeader = true;
+ $multiPart = new MultiPart();
+ $mimeMessageLines = preg_split("/(\n)/", $mimeMessage);
// add given headers, e.g. coming from HTTP headers
if (count($headers) > 0) {
- foreach ($headers as $name => $value) {
- if ($name === 'Content-Type') {
- self::parseContentTypeHeader($multipart, $name, $value);
- $boundary = $multipart->getHeader('Content-Type', 'boundary');
- $start = $multipart->getHeader('Content-Type', 'start');
- } else {
- $multipart->setHeader($name, $value);
- }
- }
- $inHeader = false;
+ self::setMultiPartHeaders($multiPart, $headers);
+ $hasHttpRequestHeaders = self::HAS_HTTP_REQUEST_HEADERS;
+ } else {
+ $hasHttpRequestHeaders = self::HAS_NO_HTTP_REQUEST_HEADERS;
}
- $content = '';
- $currentPart = $multipart;
- $lines = preg_split("/(\r\n)|(\n)/", $mimeMessage);
- if (self::hasBoundary($lines)) {
- foreach ($lines as $line) {
- // ignore http status code and POST *
- if (substr($line, 0, 5) == 'HTTP/' || substr($line, 0, 4) == 'POST') {
+ if (self::hasBoundary($mimeMessageLines)) {
+ $parsedPartList = self::getPartsFromMimeMessageLines(
+ $multiPart,
+ $mimeMessageLines,
+ $hasHttpRequestHeaders
+ );
+ 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;
}
- if (isset($currentHeader)) {
- if (isset($line[0]) && ($line[0] === ' ' || $line[0] === "\t")) {
- $currentHeader .= $line;
- continue;
+ if (strpos($currentHeader, ':') !== false) {
+ list($headerName, $headerValue) = explode(':', $currentHeader, 2);
+ $headerValue = iconv_mime_decode($headerValue, 0, Part::CHARSET_UTF8);
+ $parsedMimeHeaders = ContentTypeParser::parseContentTypeHeader($headerName, $headerValue);
+ foreach ($parsedMimeHeaders as $parsedMimeHeader) {
+ $currentPart->setHeader(
+ $parsedMimeHeader->getName(),
+ $parsedMimeHeader->getValue(),
+ $parsedMimeHeader->getSubValue()
+ );
}
- if (strpos($currentHeader, ':') !== false) {
- list($headerName, $headerValue) = explode(':', $currentHeader, 2);
- $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);
+ $contentTypeBoundary = $multiPart->getHeader('Content-Type', 'boundary');
+ $contentTypeContentIdStart = $multiPart->getHeader('Content-Type', 'start');
}
- if ($inHeader) {
- if (trim($line) == '') {
- $inHeader = false;
- continue;
- }
- $currentHeader = $line;
+ unset($currentHeader);
+ }
+ if ($inHeader === true) {
+ if (trim($mimeMessageLine) == '') {
+ $inHeader = false;
continue;
- } else {
- if (self::isBoundary($line)) {
- if (strcmp(trim($line), '--' . $boundary) === 0) {
- if ($currentPart instanceof Part) {
- $content = substr($content, 0, -1);
- self::decodeContent($currentPart, $content);
- // 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 ($isMain === true) {
- $start = $currentPart->getHeader('Content-ID');
- }
- $multipart->addPart($currentPart, $isMain);
- }
- $currentPart = new Part();
- $hitFirstBoundary = true;
- $inHeader = true;
- $content = '';
- } elseif (strcmp(trim($line), '--' . $boundary . '--') === 0) {
- $content = substr($content, 0, -1);
- self::decodeContent($currentPart, $content);
+ }
+ $currentHeader = $mimeMessageLine;
+ continue;
+ } else {
+ if (self::isBoundary($mimeMessageLine)) {
+ if (self::isMiddleBoundary($mimeMessageLine, $contentTypeBoundary)) {
+ if ($currentPart instanceof Part) {
+ $currentPartContent = self::decodeContent(
+ $currentPart,
+ substr($messagePartStringContent, 0, -1)
+ );
+ $currentPart->setContent($currentPartContent);
// 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 ($isMain === true) {
- $start = $currentPart->getHeader('Content-ID');
+ 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);
}
- $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 {
- if ($hitFirstBoundary === false) {
- if (trim($line) !== '') {
- $inHeader = true;
- $currentHeader = $line;
- continue;
- }
- }
- $content .= $line . "\n";
+ // else block migrated from https://github.com/progmancod/BeSimpleSoap/commit/bf9437e3bcf35c98c6c2f26aca655ec3d3514694
+ // be careful to replace \r\n with \n
+ $messagePartStringContent .= $mimeMessageLine . "\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.
- * 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 \BeSimple\SoapCommon\Mime\PartHeader $part Header part
- * @param string $headerName Header name
- * @param string $headerValue Header value
- *
+ * @param ParsedPartList $parsedPartList
+ * @param MultiPart $multiPart
*/
- private static function parseContentTypeHeader(PartHeader $part, $headerName, $headerValue)
+ private static function appendPartsToMultiPart(ParsedPartList $parsedPartList, MultiPart $multiPart)
{
- if (strpos($headerValue, ';')) {
- list($value, $remainder) = explode(';', $headerValue, 2);
- $value = trim($value);
- $part->setHeader($headerName, $value);
- $remainder = trim($remainder);
- while (strlen($remainder) > 0) {
- if (!preg_match('/^([a-zA-Z0-9_-]+)=(.{1})/', $remainder, $matches)) {
- break;
+ foreach ($parsedPartList->getParts() as $parsedPart) {
+ $multiPart->addPart(
+ $parsedPart->getPart(),
+ $parsedPart->isMain()
+ );
+ }
+ }
+
+ 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];
- $delimiter = $matches[2];
- $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 {
+ $multiPart->setHeader($name, $value);
}
- } else {
- $part->setHeader($headerName, $headerValue);
}
}
/**
- * Decodes the content of a Mime part.
- *
- * @param \BeSimple\SoapCommon\Mime\Part $part Part to add content
- * @param string $content Content to decode
+ * Decodes the content of a Mime part
*
+ * @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'));
$charset = strtolower($part->getHeader('Content-Type', 'charset'));
- if ($encoding == Part::ENCODING_BASE64) {
- $content = base64_decode($content);
- } elseif ($encoding == Part::ENCODING_QUOTED_PRINTABLE) {
- $content = quoted_printable_decode($content);
+
+ if ($encoding === Part::ENCODING_BASE64) {
+ $partStringContent = base64_decode($partStringContent);
+ } 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)
@@ -203,8 +261,18 @@ class Parser
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;
}
}
\ No newline at end of file
diff --git a/src/BeSimple/SoapCommon/Mime/Parser/ContentTypeParser.php b/src/BeSimple/SoapCommon/Mime/Parser/ContentTypeParser.php
new file mode 100644
index 0000000..6e403ff
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Mime/Parser/ContentTypeParser.php
@@ -0,0 +1,64 @@
+"
+ *
+ * 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, ';');
+ }
+}
diff --git a/src/BeSimple/SoapCommon/Mime/Parser/ParsedMimeHeader.php b/src/BeSimple/SoapCommon/Mime/Parser/ParsedMimeHeader.php
new file mode 100644
index 0000000..8656873
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Mime/Parser/ParsedMimeHeader.php
@@ -0,0 +1,37 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapCommon/Mime/Parser/ParsedPart.php b/src/BeSimple/SoapCommon/Mime/Parser/ParsedPart.php
new file mode 100644
index 0000000..a9a90d1
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Mime/Parser/ParsedPart.php
@@ -0,0 +1,34 @@
+part = $part;
+ $this->isMain = $isMain;
+ }
+
+ public function getPart()
+ {
+ return $this->part;
+ }
+
+ public function isMain()
+ {
+ return $this->isMain;
+ }
+}
diff --git a/src/BeSimple/SoapCommon/Mime/Parser/ParsedPartList.php b/src/BeSimple/SoapCommon/Mime/Parser/ParsedPartList.php
new file mode 100644
index 0000000..49cd3d4
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Mime/Parser/ParsedPartList.php
@@ -0,0 +1,60 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapCommon/Mime/Part.php b/src/BeSimple/SoapCommon/Mime/Part.php
index dfddb71..2e76b93 100644
--- a/src/BeSimple/SoapCommon/Mime/Part.php
+++ b/src/BeSimple/SoapCommon/Mime/Part.php
@@ -28,57 +28,38 @@ use BeSimple\SoapCommon\Helper;
*/
class Part extends PartHeader
{
- /**
- * Encoding type base 64
- */
const ENCODING_BASE64 = 'base64';
-
- /**
- * Encoding type binary
- */
const ENCODING_BINARY = 'binary';
-
- /**
- * Encoding type eight bit
- */
const ENCODING_EIGHT_BIT = '8bit';
-
- /**
- * Encoding type seven bit
- */
const ENCODING_SEVEN_BIT = '7bit';
-
- /**
- * Encoding type quoted printable
- */
const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
- /**
- * Content.
- *
- * @var mixed
- */
+ const CHARSET_UTF8 = 'utf-8';
+
+ const CONTENT_TYPE_STREAM = 'application/octet-stream';
+ const CONTENT_TYPE_PDF = 'application/pdf';
+
+ /** @var mixed */
protected $content;
/**
- * Construct new mime object.
- *
* @param mixed $content Content
* @param string $contentType Content type
* @param string $charset Charset
* @param string $encoding Encoding
* @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->setHeader('Content-Type', $contentType);
- if (!is_null($charset)) {
- $this->setHeader('Content-Type', 'charset', $charset);
- } else {
- $this->setHeader('Content-Type', 'charset', 'utf-8');
- }
+ $this->setHeader('Content-Type', 'charset', $charset);
$this->setHeader('Content-Transfer-Encoding', $encoding);
if (is_null($contentId)) {
$contentId = $this->generateContentId();
@@ -106,6 +87,16 @@ class Part extends PartHeader
return $this->content;
}
+ public function hasContentId($contentTypeContentId)
+ {
+ return $contentTypeContentId === $this->getContentId();
+ }
+
+ public function getContentId()
+ {
+ return $this->getHeader('Content-ID');
+ }
+
/**
* Set mime content.
*
@@ -137,10 +128,10 @@ class Part extends PartHeader
{
$encoding = strtolower($this->getHeader('Content-Transfer-Encoding'));
$charset = strtolower($this->getHeader('Content-Type', 'charset'));
- if ($charset != 'utf-8') {
- $content = iconv('utf-8', $charset . '//TRANSLIT', $this->content);
+ if ($charset !== self::CHARSET_UTF8) {
+ $content = iconv(self::CHARSET_UTF8, $charset.'//TRANSLIT', $this->getContent());
} else {
- $content = $this->content;
+ $content = $this->getContent();
}
switch ($encoding) {
case self::ENCODING_BASE64:
diff --git a/src/BeSimple/SoapCommon/Mime/PartFactory.php b/src/BeSimple/SoapCommon/Mime/PartFactory.php
new file mode 100644
index 0000000..ea9b3fb
--- /dev/null
+++ b/src/BeSimple/SoapCommon/Mime/PartFactory.php
@@ -0,0 +1,33 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapCommon/SoapKernel.php b/src/BeSimple/SoapCommon/SoapKernel.php
index 005ae47..0b1a4ec 100644
--- a/src/BeSimple/SoapCommon/SoapKernel.php
+++ b/src/BeSimple/SoapCommon/SoapKernel.php
@@ -8,7 +8,7 @@ namespace BeSimple\SoapCommon;
* the chain-of-responsibility pattern).
*
* @author Christian Kerl
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapKernel
{
@@ -20,7 +20,7 @@ class SoapKernel
* @param int $attachmentType = SoapOptions::SOAP_ATTACHMENTS_TYPE_SWA|SoapOptions::ATTACHMENTS_TYPE_MTOM|SoapOptions::ATTACHMENTS_TYPE_BASE64
* @return SoapRequest
*/
- public function filterRequest(SoapRequest $request, array $filters, $attachmentType)
+ public static function filterRequest(SoapRequest $request, array $filters, $attachmentType)
{
foreach ($filters as $filter) {
if ($filter instanceof SoapRequestFilter) {
@@ -37,9 +37,9 @@ class SoapKernel
* @param SoapResponse $response SOAP response
* @param SoapRequestFilter[]|SoapResponseFilter[] $filters
* @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) {
if ($filter instanceof SoapResponseFilter) {
diff --git a/src/BeSimple/SoapCommon/SoapMessage.php b/src/BeSimple/SoapCommon/SoapMessage.php
index e2d42f1..a11c5e6 100644
--- a/src/BeSimple/SoapCommon/SoapMessage.php
+++ b/src/BeSimple/SoapCommon/SoapMessage.php
@@ -62,7 +62,7 @@ abstract class SoapMessage
/**
* Mime attachments.
*
- * @var array(\BeSimple\SoapCommon\Mime\Part)
+ * @var \BeSimple\SoapCommon\Mime\Part[]
*/
protected $attachments;
diff --git a/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php b/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php
index ad7702f..8d46324 100644
--- a/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php
+++ b/src/BeSimple/SoapCommon/SoapOptions/SoapOptions.php
@@ -66,6 +66,7 @@ class SoapOptions
$this->soapFeatures = $features;
$this->wsdlFile = $wsdlFile;
$this->wsdlCacheType = $wsdlCacheType;
+ $this->wsdlCacheDir = $wsdlCacheDir;
$this->classMap = $classMap;
$this->typeConverterCollection = $typeConverterCollection;
$this->attachmentType = $attachmentType;
diff --git a/src/BeSimple/SoapCommon/SoapOptionsBuilder.php b/src/BeSimple/SoapCommon/SoapOptionsBuilder.php
index f79051c..f2c9d8c 100644
--- a/src/BeSimple/SoapCommon/SoapOptionsBuilder.php
+++ b/src/BeSimple/SoapCommon/SoapOptionsBuilder.php
@@ -18,11 +18,11 @@ use BeSimple\SoapCommon\SoapOptions\SoapOptions;
use InvalidArgumentException;
/**
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapOptionsBuilder
{
- static public function createWithDefaults(
+ public static function createWithDefaults(
$wsdlFile,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$wsdlCacheDir = null
@@ -30,16 +30,37 @@ class SoapOptionsBuilder
return self::createWithClassMap($wsdlFile, new ClassMap(), $wsdlCacheType, $wsdlCacheDir);
}
- static public function createSwaWithClassMap(
+ public static function createSwaWithClassMap(
$wsdlFile,
ClassMap $classMap,
$wsdlCacheType = SoapOptions::SOAP_CACHE_TYPE_NONE,
$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,
ClassMap $classMap,
$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');
}
}
- $soapOptions = new SoapOptions(
+
+ return new SoapOptions(
SoapOptions::SOAP_VERSION_1_2,
SoapOptions::SOAP_ENCODING_UTF8,
SoapOptions::SOAP_CONNECTION_KEEP_ALIVE_OFF,
@@ -68,7 +90,37 @@ class SoapOptionsBuilder
new TypeConverterCollection(),
$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
+ );
}
}
diff --git a/src/BeSimple/SoapCommon/SoapRequestFactory.php b/src/BeSimple/SoapCommon/SoapRequestFactory.php
index 8d07fae..38888c4 100644
--- a/src/BeSimple/SoapCommon/SoapRequestFactory.php
+++ b/src/BeSimple/SoapCommon/SoapRequestFactory.php
@@ -7,19 +7,22 @@ class SoapRequestFactory
/**
* Factory function for SoapRequest.
*
- * @param string $location Location
- * @param string $action SOAP action
- * @param string $version SOAP version
- * @param string $contentType Content Type
- * @param string $content Content
- *
+ * @param string $location Location
+ * @param string $action SOAP action
+ * @param string $version SOAP version
+ * @param string $contentType Content Type
+ * @param string $content Content
* @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();
- // $content is if unmodified from SoapClient not a php string type!
- $request->setContent((string) $content);
+ $request->setContent($content);
$request->setLocation($location);
$request->setAction($action);
$request->setVersion($version);
@@ -27,4 +30,29 @@ class SoapRequestFactory
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
+ );
+ }
}
diff --git a/src/BeSimple/SoapCommon/SoapResponse.php b/src/BeSimple/SoapCommon/SoapResponse.php
index b6815b2..3542a28 100644
--- a/src/BeSimple/SoapCommon/SoapResponse.php
+++ b/src/BeSimple/SoapCommon/SoapResponse.php
@@ -13,14 +13,31 @@
namespace BeSimple\SoapCommon;
-use BeSimple\SoapCommon\SoapMessage;
+use BeSimple\SoapClient\SoapResponseTracingData;
/**
* SOAP response message.
*
* @author Christian Kerl
+ * @author Petr Bechyně
*/
class SoapResponse extends SoapMessage
{
+ /** @var SoapResponseTracingData */
+ protected $tracingData;
-}
\ No newline at end of file
+ public function hasTracingData()
+ {
+ return $this->tracingData !== null;
+ }
+
+ public function getTracingData()
+ {
+ return $this->tracingData;
+ }
+
+ public function setTracingData(SoapResponseTracingData $tracingData)
+ {
+ $this->tracingData = $tracingData;
+ }
+}
diff --git a/src/BeSimple/SoapCommon/Tests/ClassMapTest.php b/src/BeSimple/SoapCommon/Tests/ClassMapTest.php
index 1722bb1..4c40357 100644
--- a/src/BeSimple/SoapCommon/Tests/ClassMapTest.php
+++ b/src/BeSimple/SoapCommon/Tests/ClassMapTest.php
@@ -23,43 +23,43 @@ class ClassMapTest extends \PHPUnit_Framework_TestCase
{
public function testAll()
{
- $classmap = new ClassMap();
+ $classMap = new ClassMap();
- $this->assertSame(array(), $classmap->getAll());
+ $this->assertSame([], $classMap->getAll());
}
public function testAdd()
{
- $classmap = new ClassMap();
+ $classMap = new ClassMap();
- $classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
+ $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
$this->setExpectedException('InvalidArgumentException');
- $classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
+ $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
}
public function testGet()
{
- $classmap = new ClassMap();
+ $classMap = new ClassMap();
- $classmap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
- $this->assertSame('BeSimple\SoapCommon\ClassMap', $classmap->get('foobar'));
+ $classMap->add('foobar', 'BeSimple\SoapCommon\ClassMap');
+ $this->assertSame('BeSimple\SoapCommon\ClassMap', $classMap->get('foobar'));
- $this->setExpectedException('InvalidArgumentException');
- $classmap->get('bar');
+ $this->setExpectedException('Exception');
+ $classMap->get('bar');
}
public function testAddClassMap()
{
- $classmap1 = new ClassMap();
- $classmap2 = new ClassMap();
+ $classMap1 = new ClassMap();
+ $classMap2 = new ClassMap();
- $classmap2->add('foobar', 'BeSimple\SoapCommon\ClassMap');
- $classmap1->addClassMap($classmap2);
+ $classMap2->add('foobar', 'BeSimple\SoapCommon\ClassMap');
+ $classMap1->addClassMap($classMap2);
- $this->assertEquals(array('foobar' => 'BeSimple\SoapCommon\ClassMap'), $classmap1->getAll());
+ $this->assertEquals(['foobar' => 'BeSimple\SoapCommon\ClassMap'], $classMap1->getAll());
- $this->setExpectedException('InvalidArgumentException');
- $classmap1->addClassMap($classmap2);
+ $this->setExpectedException('Exception');
+ $classMap1->addClassMap($classMap2);
}
-}
\ No newline at end of file
+}
diff --git a/src/BeSimple/SoapServer/MimeFilter.php b/src/BeSimple/SoapServer/MimeFilter.php
index f86842a..091d51e 100644
--- a/src/BeSimple/SoapServer/MimeFilter.php
+++ b/src/BeSimple/SoapServer/MimeFilter.php
@@ -19,7 +19,7 @@ use BeSimple\SoapCommon\Mime\Part as MimePart;
use BeSimple\SoapCommon\Mime\Part;
use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter;
-use BeSimple\SoapCommon\SoapResponse;
+use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter;
/**
@@ -47,7 +47,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
return $request;
}
- public function filterResponse(SoapResponse $response, $attachmentType)
+ public function filterResponse(CommonSoapResponse $response, $attachmentType)
{
$attachmentsToSend = $response->getAttachments();
if (count($attachmentsToSend) > 0) {
diff --git a/src/BeSimple/SoapServer/SoapResponseFactory.php b/src/BeSimple/SoapServer/SoapResponseFactory.php
index c10d1ae..7c6cdd7 100644
--- a/src/BeSimple/SoapServer/SoapResponseFactory.php
+++ b/src/BeSimple/SoapServer/SoapResponseFactory.php
@@ -13,24 +13,30 @@
namespace BeSimple\SoapServer;
use BeSimple\SoapBundle\Soap\SoapAttachment;
-use BeSimple\SoapCommon\Mime\Part;
+use BeSimple\SoapClient\SoapResponseTracingData;
+use BeSimple\SoapCommon\Mime\PartFactory;
use BeSimple\SoapCommon\SoapMessage;
class SoapResponseFactory
{
/**
- * Factory function for SoapResponse.
+ * Factory function for SoapServer\SoapResponse.
*
- * @param string $content Content
- * @param string $location Location
- * @param string $action SOAP action
- * @param string $version SOAP version
- * @param SoapAttachment[] $attachments SOAP attachments
+ * @param string $content Content
+ * @param string $location Location
+ * @param string $action SOAP action
+ * @param string $version SOAP version
+ * @param SoapAttachment[] $attachments SOAP attachments
*
* @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->setContent($content);
$response->setLocation($location);
@@ -38,10 +44,9 @@ class SoapResponseFactory
$response->setVersion($version);
$contentType = SoapMessage::getContentTypeForVersion($version);
$response->setContentType($contentType);
-
if (count($attachments) > 0) {
$response->setAttachments(
- self::createAttachmentParts($attachments)
+ PartFactory::createAttachmentParts($attachments)
);
}
@@ -49,23 +54,39 @@ class SoapResponseFactory
}
/**
- * @param SoapAttachment[] $attachments SOAP attachments
- * @return Part[]
+ * Factory function for SoapServer\SoapResponse.
+ *
+ * @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 = [])
- {
- $parts = [];
- foreach ($attachments as $attachment) {
- $part = new Part(
- $attachment->getContent(),
- 'application/pdf',
- 'utf-8',
- Part::ENCODING_BINARY,
- $attachment->getId()
+ public static function createWithTracingData(
+ $content,
+ $location,
+ $action,
+ $version,
+ SoapResponseTracingData $tracingData,
+ array $attachments = []
+ ) {
+ $response = new SoapResponse();
+ $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;
}
}
diff --git a/src/BeSimple/SoapServer/SoapServer.php b/src/BeSimple/SoapServer/SoapServer.php
index 63d73d1..38e64f5 100644
--- a/src/BeSimple/SoapServer/SoapServer.php
+++ b/src/BeSimple/SoapServer/SoapServer.php
@@ -30,7 +30,7 @@ use InvalidArgumentException;
*
* @author Andreas Schamberger
* @author Christian Kerl
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapServer extends \SoapServer
{
@@ -85,16 +85,15 @@ class SoapServer extends \SoapServer
*/
public function createRequest($requestUrl, $soapAction, $requestContentType, $requestContent = null)
{
- $soapRequest = SoapRequestFactory::create(
+ $soapRequest = SoapRequestFactory::createWithContentType(
$requestUrl,
$soapAction,
$this->soapVersion,
$requestContentType,
$requestContent
);
- $soapKernel = new SoapKernel();
if ($this->soapOptions->hasAttachments()) {
- $soapRequest = $soapKernel->filterRequest(
+ $soapRequest = SoapKernel::filterRequest(
$soapRequest,
$this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType()
@@ -180,7 +179,7 @@ class SoapServer extends \SoapServer
* @param SoapAttachment[] $attachments
* @return SoapResponse
*/
- private function createResponse($requestLocation, $soapAction, $soapVersion, $responseContent = null, $attachments = [])
+ private function createResponse($requestLocation, $soapAction, $soapVersion, $responseContent = null, array $attachments = [])
{
$soapResponse = SoapResponseFactory::create(
$responseContent,
@@ -189,9 +188,8 @@ class SoapServer extends \SoapServer
$soapVersion,
$attachments
);
- $soapKernel = new SoapKernel();
if ($this->soapOptions->hasAttachments()) {
- $soapResponse = $soapKernel->filterResponse(
+ $soapResponse = SoapKernel::filterResponse(
$soapResponse,
$this->getAttachmentFilters(),
$this->soapOptions->getAttachmentType()
diff --git a/src/BeSimple/SoapServer/SoapServerBuilder.php b/src/BeSimple/SoapServer/SoapServerBuilder.php
index 597abb2..f62a2f9 100644
--- a/src/BeSimple/SoapServer/SoapServerBuilder.php
+++ b/src/BeSimple/SoapServer/SoapServerBuilder.php
@@ -18,7 +18,7 @@ use BeSimple\SoapServer\SoapOptions\SoapServerOptions;
/**
* SoapServerBuilder provides a SoapServer instance from SoapServerOptions and SoapOptions.
*
- * @author Petr Bechyně
+ * @author Petr Bechyně
*/
class SoapServerBuilder
{
diff --git a/src/BeSimple/SoapServer/Tests/Attachment/Attachment.php b/src/BeSimple/SoapServer/Tests/Attachment/Attachment.php
new file mode 100644
index 0000000..77046b1
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/Attachment/Attachment.php
@@ -0,0 +1,35 @@
+fileName = $fileName;
+ $this->contentType = $contentType;
+ $this->content = $content;
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/Attachment/AttachmentCollection.php b/src/BeSimple/SoapServer/Tests/Attachment/AttachmentCollection.php
new file mode 100644
index 0000000..364889e
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/Attachment/AttachmentCollection.php
@@ -0,0 +1,21 @@
+attachments = $attachments;
+ }
+
+ public function hasAttachments()
+ {
+ return $this->attachments !== null && count($this->attachments) > 0;
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/DummyService.php b/src/BeSimple/SoapServer/Tests/DummyService.php
new file mode 100644
index 0000000..7dce6dd
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyService.php
@@ -0,0 +1,106 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/DummyService.wsdl b/src/BeSimple/SoapServer/Tests/DummyService.wsdl
new file mode 100644
index 0000000..78ec604
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyService.wsdl
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+ User name for authorization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WSDL file for DummyService
+
+
+
+
+
diff --git a/src/BeSimple/SoapServer/Tests/DummyServiceHandler.php b/src/BeSimple/SoapServer/Tests/DummyServiceHandler.php
new file mode 100644
index 0000000..b704fe9
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyServiceHandler.php
@@ -0,0 +1,18 @@
+status = true;
+
+ return $response;
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/DummyServiceHandlerWithAttachments.php b/src/BeSimple/SoapServer/Tests/DummyServiceHandlerWithAttachments.php
new file mode 100644
index 0000000..ab3b04a
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyServiceHandlerWithAttachments.php
@@ -0,0 +1,30 @@
+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;
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/DummyServiceRequest.php b/src/BeSimple/SoapServer/Tests/DummyServiceRequest.php
new file mode 100644
index 0000000..c52afba
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyServiceRequest.php
@@ -0,0 +1,11 @@
+attachmentCollection !== null && $this->attachmentCollection->hasAttachments();
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/DummyServiceResponse.php b/src/BeSimple/SoapServer/Tests/DummyServiceResponse.php
new file mode 100644
index 0000000..87857ed
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/DummyServiceResponse.php
@@ -0,0 +1,11 @@
+attachmentCollection !== null && $this->attachmentCollection->hasAttachments();
+ }
+}
diff --git a/src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php b/src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php
index 8fa5eb6..797af77 100644
--- a/src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php
+++ b/src/BeSimple/SoapServer/Tests/SoapServerBuilderTest.php
@@ -12,30 +12,139 @@
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\SoapServerOptionsBuilder;
/**
* UnitTest for \BeSimple\SoapServer\SoapServerBuilder
*
* @author Christian Kerl
+ * @author Petr Bechyne
*/
class SoapServerBuilderTest extends \PHPUnit_Framework_TestCase
{
- public function testUnconfiguredWsdl()
- {
- $builder = $this->getSoapServerBuilder();
+ const TEST_LOCAL_WSDL_UK = SoapClientBuilderTest::TEST_LOCAL_WSDL_UK;
+ const CACHE_DIR = __DIR__ . '/../../../../cache';
- $this->setExpectedException('InvalidArgumentException');
- $builder->build();
+ public function testSoapOptionsCreateWithDefaults()
+ {
+ $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();
- $builder->withWsdl('my.wsdl');
+ $defaultOptions = SoapServerOptionsBuilder::createWithDefaults(new SoapServerHandler);
- $this->setExpectedException('InvalidArgumentException');
- $builder->build();
+ self::assertInstanceOf(SoapServerOptions::class, $defaultOptions);
+ 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=""; 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()
diff --git a/src/BeSimple/SoapServer/Tests/SoapServerHandler.php b/src/BeSimple/SoapServer/Tests/SoapServerHandler.php
new file mode 100644
index 0000000..6758491
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/SoapServerHandler.php
@@ -0,0 +1,7 @@
+
+
+
+ admin
+
+
+
+
+
+ 1
+
+
+
+
diff --git a/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.message b/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.message
new file mode 100644
index 0000000..70aeaf9
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.message
@@ -0,0 +1,15 @@
+
+
+
+ admin
+
+
+
+
+
+ 2
+ false
+
+
+
+
diff --git a/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.mimepart.message b/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.mimepart.message
new file mode 100644
index 0000000..a2b7e76
--- /dev/null
+++ b/src/BeSimple/SoapServer/Tests/testHandleRequestWithSwa.mimepart.message
@@ -0,0 +1,62 @@
+
+------=_Part_6_2094841787.1482231370463
+Content-Type: text/xml; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Content-ID:
+
+
+
+
+ admin
+
+
+
+
+
+ 3
+ true
+
+
+
+
+------=_Part_6_2094841787.1482231370463
+Content-Type: text/html; charset=us-ascii; name=test-page.html
+Content-Transfer-Encoding: 7bit
+Content-ID:
+Content-Disposition: attachment; name="test-page.html"; filename="test-page.html"
+
+
+
+
+
+ Test file page
+
+
+
+
+ Hello World!
+
+
+
+------=_Part_6_2094841787.1482231370463
+Content-Type: application/x-sh; name=testscript.sh
+Content-Transfer-Encoding: binary
+Content-ID:
+Content-Disposition: attachment; name="testscript.sh"; filename="testscript.sh"
+
+#!/bin/sh
+### ====================================================================== ###
+## ##
+## Test Script ##
+## ##
+### ====================================================================== ###
+
+------=_Part_6_2094841787.1482231370463--