├── .github └── workflows │ └── build.yml ├── .gitignore ├── Action.php ├── Exception.php ├── README.md ├── Service.php ├── codeception.yml ├── composer.json ├── docs ├── CONTRIBUTING.md └── LICENSE.md └── tests ├── Support ├── Data │ └── .gitkeep ├── UnitTester.php └── _generated │ └── .gitignore ├── Unit.suite.yml ├── Unit ├── Controller.php ├── ServiceTest.php └── _bootstrap.php ├── Yii2_config └── test.php └── _output └── .gitignore /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | php-versions: 15 | - 8.0 16 | - 8.1 17 | - 8.2 18 | name: PHP ${{ matrix.php-versions }} 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v2 22 | 23 | - name: Install PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | coverage: xdebug 27 | php-version: ${{ matrix.php-versions }} 28 | 29 | - name: Install dependencies 30 | run: composer update --prefer-dist --no-progress --no-suggest 31 | 32 | - name: Run tests 33 | run: vendor/bin/codecept run -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer vendor dir 16 | /vendor 17 | 18 | # composer itself is not needed 19 | composer.phar 20 | composer.lock 21 | 22 | # Mac DS_Store Files 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /Action.php: -------------------------------------------------------------------------------- 1 | ws is used 13 | * to differentiate these two aspects: the existence of the GET parameter indicates performing the latter action. 14 | * 15 | * Note, PHP SOAP extension is required for this action. 16 | * 17 | * @property Service $service The Web service instance. This property is read-only. 18 | * 19 | * @author Alexander Mohorev 20 | */ 21 | class Action extends \yii\base\Action 22 | { 23 | /** 24 | * @var mixed the Web service provider object or class name. 25 | * If specified as a class name, it can be a path alias. 26 | * Defaults to null, meaning the current controller is used as the service provider. 27 | */ 28 | public $provider; 29 | /** 30 | * @var string the URL for the Web service. Defaults to null, meaning 31 | * the URL for this action is used to provide Web services. 32 | * In this case, a GET parameter named {@link serviceVar} will be used to 33 | * determine whether the current request is for WSDL or Web service. 34 | */ 35 | public $serviceUrl; 36 | /** 37 | * @var array the initial property values for the {@link Service} object. 38 | * The array keys are property names of {@link Service} and the array values 39 | * are the corresponding property initial values. 40 | */ 41 | public $serviceOptions = []; 42 | /** 43 | * @var string the name of the GET parameter that differentiates a WSDL request 44 | * from a Web service request. If this GET parameter exists, the request is considered 45 | * as a Web service request; otherwise, it is a WSDL request. Defaults to 'ws'. 46 | */ 47 | public $serviceVar = 'ws'; 48 | /** 49 | * @var string the URL for WSDL. Defaults to null, meaning 50 | * the URL for this action is used to serve WSDL document. 51 | */ 52 | public $wsdlUrl; 53 | /** 54 | * @var array a list of PHP classes that are declared as complex types in WSDL. 55 | * This should be an array with WSDL types as keys and names of PHP classes as values. 56 | * A PHP class can also be specified as a path alias. 57 | * @see http://www.php.net/manual/en/soapclient.soapclient.php 58 | */ 59 | public $classMap; 60 | 61 | /** 62 | * @var Service the SOAP service instance. 63 | */ 64 | private $_service; 65 | 66 | 67 | /** 68 | * @inheritdoc 69 | */ 70 | public function init() 71 | { 72 | if ($this->provider === null) { 73 | $this->provider = $this->controller; 74 | } 75 | if ($this->serviceUrl === null) { 76 | $this->serviceUrl = Yii::$app->getUrlManager()->createAbsoluteUrl([$this->getUniqueId(), $this->serviceVar => 1]); 77 | } 78 | if ($this->wsdlUrl === null) { 79 | $this->wsdlUrl = Yii::$app->getUrlManager()->createAbsoluteUrl($this->getUniqueId()); 80 | } 81 | 82 | // Disable CSRF (Cross-Site Request Forgery) validation for this action. 83 | Yii::$app->getRequest()->enableCsrfValidation = false; 84 | } 85 | 86 | /** 87 | * Runs the action. 88 | * If the GET parameter {@link serviceVar} exists, the action handle the remote method invocation. 89 | * If not, the action will serve WSDL content; 90 | */ 91 | public function run() 92 | { 93 | Yii::$app->getResponse()->format = Response::FORMAT_RAW; 94 | 95 | if (Yii::$app->request->get($this->serviceVar, false)) { 96 | return $this->getService()->run(); 97 | } else { 98 | $response = Yii::$app->getResponse(); 99 | $response->getHeaders()->set('Content-Type', 'application/xml; charset=' . $response->charset); 100 | return $this->getService()->generateWsdl(); 101 | } 102 | } 103 | 104 | /** 105 | * Returns the Web service instance currently being used. 106 | * @return Service the Web service instance 107 | */ 108 | public function getService() 109 | { 110 | if ($this->_service === null) { 111 | $this->_service = new Service([ 112 | 'provider' => $this->provider, 113 | 'serviceUrl' => $this->serviceUrl, 114 | 'wsdlUrl' => $this->wsdlUrl 115 | ]); 116 | if (is_array($this->classMap)) { 117 | $this->_service->classMap = $this->classMap; 118 | } 119 | foreach ($this->serviceOptions as $name => $value) { 120 | $this->_service->$name = $value; 121 | } 122 | } 123 | return $this->_service; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Exception.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Exception extends \yii\base\Exception 11 | { 12 | /** 13 | * @return string the user-friendly name of this exception 14 | */ 15 | public function getName() 16 | { 17 | return 'SOAP Server Exception'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SOAP Server Extension for Yii 2 2 | =============================== 3 | 4 | Note, PHP SOAP extension is required. 5 | 6 | [![Latest Version](https://img.shields.io/github/release/mohorev/yii2-soap-server?style=flat)](https://github.com/mohorev/yii2-soap-server/releases) 7 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](docs/LICENSE.md) 8 | [![Build Status](https://github.com/mohorev/yii2-soap-server/actions/workflows/build.yml/badge.svg)](https://github.com/mohorev/yii2-soap-server/actions/workflows/build.yml) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/mongosoft/yii2-soap-server?style=flat)](https://packagist.org/packages/mongosoft/yii2-soap-server) 10 | 11 | Installation 12 | ------------ 13 | 14 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 15 | 16 | Either run 17 | 18 | ``` 19 | composer require --prefer-dist mongosoft/yii2-soap-server "*" 20 | ``` 21 | 22 | or add 23 | 24 | ```json 25 | "mongosoft/yii2-soap-server": "*" 26 | ``` 27 | 28 | to the `require` section of your `composer.json` file. 29 | 30 | Usage 31 | ----- 32 | 33 | You need to add [[mongosoft\soapserver\Action]] to web controller. 34 | 35 | Note, In a service class, a remote invokable method must be a public method with a doc 36 | comment block containing the '@soap' tag. 37 | 38 | ```php 39 | class ApiController extends Controller 40 | { 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function actions() 45 | { 46 | return [ 47 | 'hello' => 'mongosoft\soapserver\Action', 48 | ]; 49 | } 50 | 51 | /** 52 | * @param string $name 53 | * @return string 54 | * @soap 55 | */ 56 | public function getHello($name) 57 | { 58 | return 'Hello ' . $name; 59 | } 60 | } 61 | ``` 62 | 63 | In case you want to disable the WSDL mode of SoapServer, you can specify this in the `serviceOptions` parameter as indicated below. 64 | You can use this when the request is to complex for the WSDL generator. 65 | 66 | ```php 67 | /** 68 | * @inheritdoc 69 | */ 70 | public function actions() 71 | { 72 | return [ 73 | 'index' => [ 74 | 'class' => 'mongosoft\soapserver\Action', 75 | 'serviceOptions' => [ 76 | 'disableWsdlMode' => true, 77 | ] 78 | ] 79 | ]; 80 | } 81 | ``` 82 | 83 | Testing 84 | ------- 85 | 86 | ``` bash 87 | $ vendor/bin/codecept run Unit 88 | ``` 89 | 90 | ## Contributing 91 | 92 | Please see [CONTRIBUTING](docs/CONTRIBUTING.md) for details. 93 | 94 | ## Security 95 | 96 | If you discover any security related issues, please email instead of using the issue tracker. 97 | 98 | ## License 99 | 100 | The MIT License (MIT). Please see [License File](docs/LICENSE.md) for more information. 101 | -------------------------------------------------------------------------------- /Service.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Service extends Component 23 | { 24 | /** 25 | * @var string|object the web service provider class or object. 26 | * If specified as a class name, it can be a path alias. 27 | */ 28 | public $provider; 29 | /** 30 | * @var string the URL for the Web service. 31 | */ 32 | public $serviceUrl; 33 | /** 34 | * @var string the URL for WSDL. 35 | */ 36 | public $wsdlUrl; 37 | /** 38 | * @var boolean indicating if the WSDL mode of SoapServer should be disabled. 39 | */ 40 | public $disableWsdlMode = false; 41 | /** 42 | * @var Cache|array|string the cache object or the application component ID of the cache object. 43 | * The WSDL will be cached using this cache object. Note, this property has meaning only 44 | * in case [[cachingDuration]] set to non-zero value. 45 | * Starting from version 2.0.2, this can also be a configuration array for creating the object. 46 | */ 47 | public $cache = 'cache'; 48 | /** 49 | * @var integer the time in seconds that the WSDL can remain valid in cache. 50 | * Use 0 to indicate that the cached data will never expire. 51 | * @see enableCaching 52 | */ 53 | public $cachingDuration = 0; 54 | /** 55 | * @var boolean whether to enable caching WSDL 56 | */ 57 | public $enableCaching = false; 58 | /** 59 | * @var string encoding of the Web service. Defaults to 'utf-8'. 60 | */ 61 | public $encoding = 'utf-8'; 62 | /** 63 | * @var array a list of classes that are declared as complex types in WSDL. 64 | * This should be an array with WSDL types as keys and names of PHP classes as values. 65 | * A PHP class can also be specified as a path alias. 66 | * @see http://www.php.net/manual/en/soapserver.soapserver.php 67 | */ 68 | public $classMap = []; 69 | /** 70 | * @var string actor of the SOAP service. Defaults to null. 71 | */ 72 | public $actor; 73 | /** 74 | * @var string SOAP version (e.g. '1.1' or '1.2'). Defaults to null. 75 | */ 76 | public $soapVersion; 77 | /** 78 | * @var integer the persistence mode of the SOAP server. 79 | * @see http://www.php.net/manual/en/soapserver.setpersistence.php 80 | */ 81 | public $persistence; 82 | 83 | 84 | /** 85 | * @inheritdoc 86 | */ 87 | public function init() 88 | { 89 | parent::init(); 90 | if ($this->provider === null) { 91 | throw new InvalidConfigException('The "provider" property must be set.'); 92 | } 93 | if ($this->serviceUrl === null) { 94 | throw new InvalidConfigException('The "serviceUrl" property must be set.'); 95 | } 96 | if ($this->wsdlUrl === null) { 97 | throw new InvalidConfigException('The "wsdlUrl" property must be set.'); 98 | } 99 | if ($this->enableCaching) { 100 | $this->cache = Instance::ensure($this->cache, Cache::class); 101 | } 102 | 103 | if (YII_DEBUG) { 104 | ini_set('soap.wsdl_cache_enabled', 0); 105 | } 106 | } 107 | 108 | /** 109 | * Generates the WSDL as defined by the provider. 110 | * The cached version may be used if the WSDL is found valid in cache. 111 | * @return string the generated WSDL 112 | */ 113 | public function generateWsdl() 114 | { 115 | $providerClass = get_class($this->provider); 116 | if ($this->enableCaching) { 117 | $key = [ 118 | __METHOD__, 119 | $providerClass, 120 | $this->serviceUrl, 121 | ]; 122 | $result = $this->cache->get($key); 123 | if ($result === false) { 124 | $result = $this->generateWsdlInternal($providerClass, $this->serviceUrl); 125 | $this->cache->set($key, $result, $this->cachingDuration); 126 | } 127 | return $result; 128 | } else { 129 | return $this->generateWsdlInternal($providerClass, $this->serviceUrl); 130 | } 131 | } 132 | 133 | /** 134 | * @see Service::generateWsdl() 135 | */ 136 | protected function generateWsdlInternal($className, $serviceUrl) 137 | { 138 | $wsdlGenerator = new PHPClass2WSDL($className, $serviceUrl); 139 | $wsdlGenerator->generateWSDL(true); 140 | return $wsdlGenerator->dump(); 141 | } 142 | 143 | /** 144 | * Handles the web service request. 145 | */ 146 | public function run() 147 | { 148 | header('Content-Type: text/xml;charset=' . $this->encoding); 149 | 150 | if ($this->disableWsdlMode) { 151 | $server = new SoapServer(null, array_merge(['uri' => $this->serviceUrl], $this->getOptions())); 152 | } else { 153 | $server = new SoapServer($this->wsdlUrl, $this->getOptions()); 154 | } 155 | try { 156 | if ($this->persistence !== null) { 157 | $server->setPersistence($this->persistence); 158 | } 159 | if (is_string($this->provider)) { 160 | $provider = $this->provider; 161 | $provider = new $provider(); 162 | } else { 163 | $provider = $this->provider; 164 | } 165 | $server->setObject($provider); 166 | 167 | ob_start(); 168 | $server->handle(); 169 | $result = ob_get_contents(); 170 | ob_end_clean(); 171 | 172 | return $result; 173 | } catch (\Exception $e) { 174 | Yii::error($e->getMessage(), __METHOD__); 175 | // We need to end application explicitly because of http://bugs.php.net/bug.php?id=49513 176 | $server->fault($e->getCode(), $e->getMessage()); 177 | exit(1); 178 | } 179 | } 180 | 181 | /** 182 | * @return array options for creating SoapServer instance 183 | * @see http://www.php.net/manual/en/soapserver.soapserver.php 184 | */ 185 | protected function getOptions() 186 | { 187 | $options = []; 188 | if ($this->soapVersion === '1.1') { 189 | $options['soap_version'] = SOAP_1_1; 190 | } elseif ($this->soapVersion === '1.2') { 191 | $options['soap_version'] = SOAP_1_2; 192 | } 193 | if ($this->actor !== null) { 194 | $options['actor'] = $this->actor; 195 | } 196 | foreach ($this->classMap as $type => $className) { 197 | if (is_int($type)) { 198 | $type = $className; 199 | } 200 | $options['classmap'][$type] = $className; 201 | } 202 | $options['encoding'] = $this->encoding; 203 | return $options; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | namespace: Tests 2 | support_namespace: Support 3 | paths: 4 | tests: tests 5 | output: tests/_output 6 | data: tests/Support/Data 7 | support: tests/Support 8 | envs: tests/_envs 9 | actor_suffix: Tester 10 | extensions: 11 | enabled: 12 | - Codeception\Extension\RunFailed 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongosoft/yii2-soap-server", 3 | "description": "SOAP Server Extension for Yii 2", 4 | "keywords": [ 5 | "yii2", 6 | "extension", 7 | "soap", 8 | "wsdl", 9 | "server" 10 | ], 11 | "homepage": "https://github.com/mongosoft/yii2-soap-server", 12 | "type": "yii2-extension", 13 | "license": "MIT", 14 | "support": { 15 | "issues": "https://github.com/mongosoft/yii2-soap-server/issues?state=open", 16 | "source": "https://github.com/mongosoft/yii2-soap-server" 17 | }, 18 | "authors": [ 19 | { 20 | "name": "Alexander Mohorev", 21 | "email": "dev.mohorev@gmail.com" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=7.2", 26 | "ext-soap": "*", 27 | "yiisoft/yii2": "^2.0.35", 28 | "php2wsdl/php2wsdl": "*" 29 | }, 30 | "require-dev": { 31 | "yidas/yii2-bower-asset": "*", 32 | "codeception/codeception": "*", 33 | "codeception/module-asserts": "*", 34 | "ext-simplexml": "*" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "mongosoft\\soapserver\\": "" 39 | } 40 | }, 41 | "config": { 42 | "allow-plugins": { 43 | "yiisoft/yii2-composer": true 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/mohorev/yii2-soap-server). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ vendor/bin/codecept run Unit 29 | ``` 30 | 31 | 32 | **Happy coding**! -------------------------------------------------------------------------------- /docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2017 GitHub Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/Support/Data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohorev/yii2-soap-server/34133bc041f773fca2c5e42aaf18ad6e523e76b5/tests/Support/Data/.gitkeep -------------------------------------------------------------------------------- /tests/Support/UnitTester.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'class' => 'mongosoft\soapserver\Action', 12 | ], 13 | ]; 14 | } 15 | 16 | /** 17 | * @param string $name Your name 18 | * @return string 19 | * @soap 20 | */ 21 | public function getHello(string $name): string 22 | { 23 | return 'Hello ' . $name; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Unit/ServiceTest.php: -------------------------------------------------------------------------------- 1 | $controller, 26 | 'serviceUrl' => 'http://test-url/', 27 | 'wsdlUrl' => 'http://wsdl-url/', 28 | ]); 29 | $wsdl = $soapService->generateWsdl(); 30 | $xml = simplexml_load_string($wsdl); 31 | 32 | $this->assertTrue($xml instanceof SimpleXMLElement); 33 | $this->assertEquals('definitions', (string) $xml->getName()); 34 | 35 | $operation = $xml->xpath('//wsdl:operation[@name="getHello"]'); 36 | $this->assertTrue($operation[0] instanceof SimpleXMLElement); 37 | 38 | $address = $xml->xpath('//soap:address'); 39 | $location = (string) $address[0]->attributes()->location; 40 | $this->assertEquals('http://test-url/', $location); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'basic-tests', 8 | 'basePath' => dirname(__DIR__), 9 | 'aliases' => [ 10 | '@web' => '@app' 11 | ], 12 | 'controllerNamespace' => 'app\tests' 13 | ]; 14 | -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------