├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── zoap.php ├── src ├── Demo │ ├── DemoProvider.php │ ├── DemoService.php │ └── Types │ │ ├── KeyValue.php │ │ └── Product.php ├── Resources │ └── views │ │ └── fault.blade.php ├── ZoapController.php ├── ZoapServiceProvider.php └── routes.php └── tests └── Zoap.postman_collection.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This document tracks changes in release versions of **Zoap**. This project adheres to the [Semantic Versioning](http://semver.org/spec/v2.0.0.html) standard. 4 | 5 | ## Version [1.0.5](https://github.com/viewflex/zoap/tree/1.0.5) - 2021-02-24 6 | 7 | ### Changed 8 | 9 | - Use laminas-soap package instead of zend-soap. 10 | 11 | ## Version [1.0.4](https://github.com/viewflex/zoap/tree/1.0.4) - 2019-09-04 12 | 13 | ### Changed 14 | 15 | - Use Arr::add() instead of deprecated array_add(). 16 | 17 | ## Version [1.0.3](https://github.com/viewflex/zoap/tree/1.0.3) - 2018-12-9 18 | 19 | ### Added 20 | 21 | - Config for server options. 22 | 23 | ## Version [1.0.2](https://github.com/viewflex/zoap/tree/1.0.2) - 2018-05-25 24 | 25 | ### Removed 26 | 27 | - Line requiring laravel/framework in composer.json. 28 | 29 | ## Version [1.0.1](https://github.com/viewflex/zoap/tree/1.0.1) - 2018-05-25 30 | 31 | ### Added 32 | 33 | - Changelog. 34 | 35 | ### Changed 36 | 37 | - Minor refactoring for Lumen compatibility 38 | 39 | ## Version [1.0.0](https://github.com/viewflex/zoap/tree/1.0) - 2018-04-26 40 | 41 | Initial public release. 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Adam Sailor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zoap 2 | 3 | [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](LICENSE.md) 4 | 5 | Instant SOAP server for Laravel and Lumen, turns any class into a WS-I compliant SOAP service, with automatic discovery of WSDL definitions. Wraps the Laminas SOAP components to provide easy declarative configuration of services, requiring no additional coding. 6 | 7 | ## Overview 8 | 9 | ### System Requirements 10 | 11 | Laravel or Lumen framework, version 5.2 or greater. 12 | 13 | ### Basic Steps 14 | 15 | Setting up services is quick and painless: 16 | 17 | * Install this package in your Laravel or Lumen application. 18 | * Publish the config file for customization. 19 | * Define configurations for your services. 20 | 21 | ### Examples 22 | 23 | There is a Demo service already configured and ready to test. This can be used as a template for creating your own services from existing classes. WSDL auto-discovery and generation depends on you having properly annotated your service class attributes and methods with PHP DocBlocks, as illustrated in the `DemoService` class and explained below. 24 | 25 | ### Architecture 26 | 27 | This package uses the `document/literal wrapped` pattern in SOAP communications and WSDL generation, but if necessary, the `ZoapController` class can be extended to deploy an alternate pattern. 28 | 29 | 30 | ## Installation 31 | 32 | From your Laravel or Lumen application's root directory, install via Composer: 33 | 34 | ```bash 35 | composer require viewflex/zoap 36 | ``` 37 | 38 | After installing, add the `ZoapServiceProvider` to the list of service providers: 39 | 40 | ### For Laravel 41 | 42 | Add this line in `config/app.php`. If you are using Laravel version 5.5 or greater, this step is not necessary. 43 | 44 | ```php 45 | Viewflex\Zoap\ZoapServiceProvider::class, 46 | ``` 47 | 48 | ### For Lumen 49 | 50 | Add this line in `bootstrap/app.php`: 51 | 52 | ```php 53 | $app->register(Viewflex\Zoap\ZoapServiceProvider::class); 54 | ``` 55 | 56 | You may also want to install the [irazasyed/larasupport](https://github.com/irazasyed/larasupport) package, which adds a few basic Laravel features to Lumen, including support for publishing package files. This will allow you to publish the Zoap config and view files for customization as described [below](#configuration); otherwise, you can just copy those files manually from the package to their published locations. 57 | 58 | ## Configuration 59 | 60 | The `zoap.php` config file contains both general settings and configuration of individual services. 61 | 62 | Run this command to publish the `zoap.php` config file to the project's `config` directory for customization: 63 | 64 | ```bash 65 | php artisan vendor:publish --tag='zoap' 66 | ``` 67 | 68 | This will also publish the SoapFault response template to your project's `resources/views/vendor/zoap` directory. 69 | 70 | ### Logging 71 | 72 | When enabled, full error information, including trace stack, will be logged for exceptions. 73 | 74 | 75 | ### Services 76 | 77 | The `ZoapController` class configures the server for the service matching the `key` route parameter (see [Routing](#routing) below). In this way you can serve any number of classes with your SOAP server, simply by defining them here. 78 | 79 | The Demo service is provided as an example; it's configuration is shown here: 80 | 81 | ```php 82 | 'services' => [ 83 | 84 | 'demo' => [ 85 | 'name' => 'Demo', 86 | 'class' => 'Viewflex\Zoap\Demo\DemoService', 87 | 'exceptions' => [ 88 | 'Exception' 89 | ], 90 | 'types' => [ 91 | 'keyValue' => 'Viewflex\Zoap\Demo\Types\KeyValue', 92 | 'product' => 'Viewflex\Zoap\Demo\Types\Product' 93 | ], 94 | 'strategy' => 'ArrayOfTypeComplex', 95 | 'headers' => [ 96 | 'Cache-Control' => 'no-cache, no-store' 97 | ], 98 | 'options' => [] 99 | ] 100 | 101 | ], 102 | ``` 103 | 104 | #### Name 105 | 106 | Specify the name of the service as it will appear in the generated WSDL file. 107 | 108 | #### Class 109 | 110 | Specify the class you want to serve. Public attributes and methods of this class will be made available by the SOAP server. 111 | 112 | #### Exceptions 113 | 114 | List any exceptions you want caught and converted to a `SoapFault`. Using `Exception` will catch all exceptions, or you can be more specific and list individual child exceptions. Any exceptions not on this whitelist may return unpredictable results, including no result at all. We don't want to let the server return a `SoapFault` directly, which could expose a stack trace; instead the exception is caught and then returned as a `SoapFault` with the proper message. 115 | 116 | #### Types 117 | 118 | Add complex types as necessary - typically auto-discovery will find them, but if not they can be specified here - auto-discovery will not redundantly add the same type again anyway. 119 | 120 | #### Strategy 121 | 122 | Specify one of these `ComplexTypeStrategyInterface` implementations to use in auto-discovery: 123 | 124 | * AnyType 125 | * ArrayOfTypeComplex 126 | * ArrayOfTypeSequence 127 | * DefaultComplexType 128 | 129 | If not specified, the `ArrayOfTypeComplex` strategy will be used. 130 | 131 | #### Headers 132 | 133 | A `Content-Type` header of 'application/xml; charset=utf-8' is set automatically if not otherwise specified here. Specify any additional HTTP response headers required. 134 | 135 | #### Options 136 | 137 | Specify an array of server options for this service (optional). 138 | 139 | ## Routing 140 | 141 | The package routes file routes the Demo service: 142 | 143 | ```php 144 | app()->router->get('zoap/{key}/server', [ 145 | 'as' => 'zoap.server.wsdl', 146 | 'uses' => '\Viewflex\Zoap\ZoapController@server' 147 | ]); 148 | 149 | app()->router->post('zoap/{key}/server', [ 150 | 'as' => 'zoap.server', 151 | 'uses' => '\Viewflex\Zoap\ZoapController@server' 152 | ]); 153 | ``` 154 | 155 | Use this route or create new routes as necessary to access your SOAP services on the `ZoapController` class, using the same URL parameter `{key}` to indicate the key for a service configuration. The key 'demo' is used to look up the Demo [service configuration](#configuration). 156 | 157 | ## Usage 158 | 159 | SOAP is a complex specification with various implementations, and can be difficult to work with for a number of reasons. This package abstracts much of the implementation details away from the developer. 160 | 161 | It remains for you to define your SOAP API using PHP DocBlock notation on all public class attributes and methods; this is used by the auto-discovery process to define your service. See the [Demo](#demo) section below to get a walk-through of a real implementation provided as an example to get you started. 162 | 163 | 164 | ## Demo 165 | 166 | The Demo SOAP service provided with this package is a simple implementation example, with commonly used configuration values. The `DemoService` class references a fictional provider (`DemoProvider` class) which returns some hard-coded results, simply to illustrate the concept of application functionality exposed as a SOAP service. 167 | 168 | Example requests for the Demo service methods are provided below, along with the expected responses. Replace 'http://example.com' in the requests with the actual domain of your Laravel application. 169 | 170 | The Demo service class provides an example of how method parameters and return values are automatically transformed by the server to the appropriate data formats. Shown here is the DocBlock of the `getProducts` method in the `DemoService` class: 171 | 172 | ```php 173 | /** 174 | * Returns an array of products by search criteria. 175 | * 176 | * @param \Viewflex\Zoap\Types\KeyValue[] $criteria 177 | * @param string $token 178 | * @param string $user 179 | * @param string $password 180 | * @return \Viewflex\Zoap\Types\Product[] 181 | * @throws SoapFault 182 | */ 183 | public function getProducts($criteria = [], $token = '', $user = '', $password = '') 184 | ``` 185 | 186 | This method returns an array of `Product` objects, wrapped and formatted as an XML string, [as shown below](#getproducts). 187 | 188 | 189 | ### WSDL Generation 190 | 191 | A SOAP server must be provided with a WSDL file to be able to recognize the methods and data models it is expected to handle, but writing one manually is difficult and error-prone, given the complexity of the specification and the lack of good documentation. This package uses auto-discovery to generate the WSDL file automatically. 192 | 193 | The WSDL definition of the Demo service can be obtained via GET request to the Demo route with the empty URL parameter `'wsdl'`: 194 | 195 | http://example.com/zoap/demo/server?wsdl 196 | 197 | It should return a complete WSDL file describing the Demo service. 198 | 199 | ```xml 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | Authenticates user/password, returning status of true with token, or throws SoapFault. 303 | 304 | 305 | 306 | 307 | Returns boolean authentication result using given token or user/password. 308 | 309 | 310 | 311 | 312 | Returns a product by id. 313 | 314 | 315 | 316 | 317 | Returns an array of products by search criteria. 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | ``` 392 | 393 | ### Service Methods 394 | 395 | To access a service method, use a POST request with `Content-Type` header of 'application/xml' or 'text/xml', and body content as shown below. The `user`, `password` and `token` parameters will be authenticated against hard-coded values, so you can see the failure result if you change them. Also included in the Demo service are methods for getting a single product or an array of products, to illustrate the formatting of results from methods returning complex objects. 396 | 397 | #### auth 398 | 399 | ##### Request 400 | 401 | ```xml 402 | 403 | 404 | 405 | 406 | test@test.com 407 | tester 408 | 409 | 410 | 411 | ``` 412 | 413 | ##### Response 414 | 415 | ```xml 416 | 417 | 418 | 419 | 420 | 421 | 422 | status 423 | true 424 | 425 | 426 | token 427 | tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX 428 | 429 | 430 | 431 | 432 | 433 | ``` 434 | 435 | #### ping 436 | 437 | ##### Request 438 | 439 | ```xml 440 | 441 | 442 | 443 | 444 | tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX 445 | 446 | 447 | 448 | ``` 449 | 450 | ##### Response 451 | 452 | ```xml 453 | 454 | 455 | 456 | 457 | true 458 | 459 | 460 | 461 | ``` 462 | 463 | 464 | #### getProduct 465 | 466 | ##### Request 467 | 468 | ```xml 469 | 470 | 471 | 472 | 473 | 456 474 | tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX 475 | 476 | 477 | 478 | ``` 479 | ##### Response 480 | 481 | ```xml 482 | 483 | 484 | 485 | 486 | 487 | 456 488 | North Face Summit Ski Jacket 489 | Outerwear 490 | Women 491 | 249.98 492 | 493 | 494 | 495 | 496 | ``` 497 | 498 | 499 | #### getProducts 500 | 501 | ##### Request 502 | 503 | ```xml 504 | 505 | 506 | 507 | 508 | 509 | 510 | category 511 | Outerwear 512 | 513 | 514 | tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX 515 | 516 | 517 | 518 | ``` 519 | 520 | ##### Response 521 | 522 | ```xml 523 | 524 | 525 | 526 | 527 | 528 | 529 | 456 530 | North Face Summit Ski Jacket 531 | Outerwear 532 | Women 533 | 249.98 534 | 535 | 536 | 789 537 | Marmot Crew Neck Base Layer 538 | Outerwear 539 | Men 540 | 95.29 541 | 542 | 543 | 544 | 545 | 546 | ``` 547 | 548 | ## Tests 549 | 550 | Using an HTTP client such as [Postman](https://www.getpostman.com/), you can test your services directly with XML requests. A [Postman collection file](tests/Zoap.postman_collection.json) for the Demo service is included in this package's `tests` directory; you can run the collection's test suite from within Postman or on the command line via Newman (see Postman docs). Set the collection variable `domain` to your Laravel app's actual domain (instead of 'http://example.com'). 551 | 552 | ## License 553 | 554 | This software is offered for use under the [MIT License](LICENSE.md). 555 | 556 | ## Changelog 557 | 558 | Release versions are tracked in the [Changelog](CHANGELOG.md). 559 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viewflex/zoap", 3 | "description": "For quickly deploying SOAP services in Laravel and Lumen, with auto-discovery of WSDL definitions.", 4 | "keywords": ["laravel", "lumen", "soap", "soap server", "soap api"], 5 | "authors": [ 6 | { 7 | "name": "Adam Sailor", 8 | "email": "viewflex.contact@gmail.com" 9 | } 10 | ], 11 | "license": "MIT", 12 | "require": { 13 | "laminas/laminas-soap": "^2.9" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Viewflex\\Zoap\\": "src/" 18 | } 19 | }, 20 | "extra": { 21 | "laravel": { 22 | "providers": [ 23 | "Viewflex\\Zoap\\ZoapServiceProvider" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/zoap.php: -------------------------------------------------------------------------------- 1 | [ 8 | 9 | 'demo' => [ 10 | 'name' => 'Demo', 11 | 'class' => 'Viewflex\Zoap\Demo\DemoService', 12 | 'exceptions' => [ 13 | 'Exception' 14 | ], 15 | 'types' => [ 16 | 'keyValue' => 'Viewflex\Zoap\Demo\Types\KeyValue', 17 | 'product' => 'Viewflex\Zoap\Demo\Types\Product' 18 | ], 19 | 'strategy' => 'ArrayOfTypeComplex', 20 | 'headers' => [ 21 | 'Cache-Control' => 'no-cache, no-store' 22 | ], 23 | 'options' => [] 24 | ] 25 | 26 | ], 27 | 28 | 29 | // Log exception trace stack? 30 | 31 | 'logging' => true, 32 | 33 | 34 | // Mock credentials for demo. 35 | 36 | 'mock' => [ 37 | 'user' => 'test@test.com', 38 | 'password' => 'tester', 39 | 'token' => 'tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX' 40 | ], 41 | 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /src/Demo/DemoProvider.php: -------------------------------------------------------------------------------- 1 | 'true', 'token' => Provider::getToken($user)]; 33 | } else { 34 | header("Status: 401"); 35 | throw new SoapFault('SOAP-ENV:Client', 'Incorrect credentials.'); 36 | 37 | } 38 | 39 | } 40 | 41 | /** 42 | * Returns boolean authentication result using given token or user/password. 43 | * 44 | * @param string $token 45 | * @param string $user 46 | * @param string $password 47 | * @return bool 48 | */ 49 | public function ping($token = '', $user = '', $password = '') 50 | { 51 | return Provider::authenticate($token, $user, $password); 52 | } 53 | 54 | /** 55 | * Returns a product by id. 56 | * 57 | * @param int $productId 58 | * @param string $token 59 | * @param string $user 60 | * @param string $password 61 | * @return \Viewflex\Zoap\Demo\Types\Product 62 | * @throws SoapFault 63 | */ 64 | public function getProduct($productId, $token = '', $user = '', $password = '') 65 | { 66 | 67 | if (! $productId) { 68 | header("Status: 400"); 69 | throw new SoapFault('SOAP-ENV:Client', 'Please specify product id.'); 70 | } 71 | 72 | if (! Provider::authenticate($token, $user, $password)) { 73 | header("Status: 401"); 74 | throw new SoapFault('SOAP-ENV:Client', 'Incorrect credentials.'); 75 | } 76 | 77 | return Provider::findProduct($productId); 78 | } 79 | 80 | /** 81 | * Returns an array of products by search criteria. 82 | * 83 | * @param \Viewflex\Zoap\Demo\Types\KeyValue[] $criteria 84 | * @param string $token 85 | * @param string $user 86 | * @param string $password 87 | * @return \Viewflex\Zoap\Demo\Types\Product[] 88 | * @throws SoapFault 89 | */ 90 | public function getProducts($criteria = [], $token = '', $user = '', $password = '') 91 | { 92 | 93 | if (! Provider::authenticate($token, $user, $password)) { 94 | header("Status: 401"); 95 | throw new SoapFault('SOAP-ENV:Client', 'Incorrect credentials.'); 96 | } 97 | 98 | return Provider::findProductsBy(self::arrayOfKeyValueToArray($criteria)); 99 | 100 | } 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Utility 105 | |-------------------------------------------------------------------------- 106 | */ 107 | 108 | /** 109 | * Convert array of KeyValue objects to associative array, non-recursively. 110 | * 111 | * @param \Viewflex\Zoap\Demo\Types\KeyValue[] $objects 112 | * @return array 113 | */ 114 | protected static function arrayOfKeyValueToArray($objects) 115 | { 116 | $return = array(); 117 | foreach ($objects as $object) { 118 | $return[$object->key] = $object->value; 119 | } 120 | 121 | return $return; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Demo/Types/KeyValue.php: -------------------------------------------------------------------------------- 1 | key = $key; 26 | $this->value = $value; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/Demo/Types/Product.php: -------------------------------------------------------------------------------- 1 | id = $id; 45 | $this->name = $name; 46 | $this->category = $category; 47 | $this->subcategory = $subcategory; 48 | $this->price = $price; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Resources/views/fault.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {!! $faultcode !!} 6 | {!! $faultstring !!} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ZoapController.php: -------------------------------------------------------------------------------- 1 | name = $config['name']; 76 | $this->service = $config['class']; 77 | $this->endpoint = self::currentUrlRoot(); 78 | $this->exceptions = $config['exceptions']; 79 | $this->types = $config['types']; 80 | 81 | $strategies = [ 82 | 'AnyType', 83 | 'ArrayOfTypeComplex', 84 | 'ArrayOfTypeSequence', 85 | 'DefaultComplexType' 86 | ]; 87 | 88 | $strategy = ($config['strategy']) ? : 'ArrayOfTypeComplex'; 89 | 90 | if (! in_array($strategy, $strategies)) { 91 | throw new \Exception('Please specify a valid complex type strategy.'); 92 | } 93 | 94 | $strategy = "Laminas\\Soap\\Wsdl\\ComplexTypeStrategy\\" . $strategy; 95 | $this->strategy = new $strategy(); 96 | 97 | $this->headers = $config['headers']; 98 | $this->options = array_key_exists('options', $config) ? $config['options'] : []; 99 | 100 | if (! array_key_exists('Content-Type', $this->headers)) { 101 | $this->headers = Arr::add($this->headers, 'Content-Type', 'application/xml; charset=utf-8'); 102 | } 103 | 104 | ini_set('soap.wsdl_cache_enable', 0); 105 | ini_set('soap.wsdl_cache_ttl', 0); 106 | 107 | } 108 | 109 | /** 110 | * Return results of a call to the specified service. 111 | * 112 | * @param $key 113 | * @return Factory|Response|View 114 | */ 115 | public function server($key) 116 | { 117 | $output = new Response(); 118 | ob_start(); 119 | 120 | try { 121 | 122 | $this->init($key); 123 | 124 | foreach($this->headers as $key => $value) { 125 | $output->headers->set($key, $value); 126 | } 127 | 128 | if (isset($_GET['wsdl'])) { 129 | 130 | // Create wsdl object and register type(s). 131 | $wsdl = new Wsdl('wsdl', $this->endpoint); 132 | 133 | foreach($this->types as $key => $class) { 134 | $wsdl->addType($class, $key); 135 | } 136 | 137 | // Set type(s) on strategy object. 138 | $this->strategy->setContext($wsdl); 139 | 140 | foreach($this->types as $key => $class) { 141 | $this->strategy->addComplexType($class); 142 | } 143 | 144 | // Auto-discover and output xml. 145 | $discover = new AutoDiscover($this->strategy); 146 | $discover->setBindingStyle(array('style' => 'document')); 147 | $discover->setOperationBodyStyle(array('use' => 'literal')); 148 | $discover->setClass($this->service); 149 | $discover->setUri($this->endpoint); 150 | $discover->setServiceName($this->name); 151 | echo $discover->toXml(); 152 | 153 | } else { 154 | 155 | $server = new Server($this->endpoint . '?wsdl'); 156 | $server->setClass(new DocumentLiteralWrapper(new $this->service())); 157 | $server->registerFaultException($this->exceptions); 158 | $server->setOptions($this->options); 159 | 160 | // Intercept response, then decide what to do with it. 161 | $server->setReturnResponse(true); 162 | $response = $server->handle(); 163 | 164 | // Deal with a thrown exception that was converted into a SoapFault. 165 | // SoapFault thrown directly in a service class bypasses this code. 166 | if ($response instanceof SoapFault) { 167 | 168 | $output->headers->set("Status", 500); 169 | echo self::serverFault($response); 170 | 171 | } else { 172 | 173 | echo $response; 174 | 175 | } 176 | 177 | } 178 | 179 | 180 | } catch (\Exception $e) { 181 | 182 | $output->headers->set("Status", 500); 183 | echo self::serverFault($e); 184 | 185 | } 186 | 187 | $output->setContent(ob_get_clean()); 188 | return $output; 189 | 190 | } 191 | 192 | /** 193 | * Get the current absolute URL path, minus the query string. 194 | * 195 | * @return string 196 | */ 197 | public static function currentUrlRoot() 198 | { 199 | $url = url(app()->request->server()['REQUEST_URI']); 200 | $pos = strpos($url, '?'); 201 | return $pos ? substr($url, 0, $pos) : $url; 202 | } 203 | 204 | /** 205 | * Log message if logging is enabled in config, return input fluently. 206 | * 207 | * @param string $message 208 | * @return string 209 | */ 210 | public static function log($message = '') 211 | { 212 | if(config('zoap.logging', false)) { 213 | Log::info($message); 214 | } 215 | 216 | return $message; 217 | } 218 | 219 | /** 220 | * Return error response and log stack trace. 221 | * 222 | * @param \Exception $exception 223 | * @return Factory|View 224 | */ 225 | public static function serverFault(\Exception $exception) 226 | { 227 | self::log($exception->getTraceAsString()); 228 | $faultcode = 'SOAP-ENV:Server'; 229 | $faultstring = $exception->getMessage(); 230 | return view('zoap::fault', compact('faultcode', 'faultstring')); 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /src/ZoapServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/Resources/views', 'zoap'); 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Publish Views and Config 30 | |-------------------------------------------------------------------------- 31 | */ 32 | 33 | $this->publishes([ 34 | __DIR__ . '/Resources/views' => base_path('resources/views/vendor/zoap'), 35 | __DIR__ . '/../config/zoap.php' => base_path('config/zoap.php') 36 | ], 'zoap'); 37 | 38 | } 39 | 40 | /** 41 | * Register the application services. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Merge User-Customized Config Values 51 | |-------------------------------------------------------------------------- 52 | */ 53 | 54 | $this->mergeConfigFrom( 55 | __DIR__ . '/../config/zoap.php', 'zoap' 56 | ); 57 | 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Include the Package Routes File. 62 | |-------------------------------------------------------------------------- 63 | */ 64 | 65 | require __DIR__ . '/routes.php'; 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | router->get('zoap/{key}/server', [ 4 | 'as' => 'zoap.server.wsdl', 5 | 'uses' => '\Viewflex\Zoap\ZoapController@server' 6 | ]); 7 | 8 | app()->router->post('zoap/{key}/server', [ 9 | 'as' => 'zoap.server', 10 | 'uses' => '\Viewflex\Zoap\ZoapController@server' 11 | ]); 12 | -------------------------------------------------------------------------------- /tests/Zoap.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "df45e902-8c00-ebee-dc4a-1dd45c4aae62", 4 | "name": "Zoap", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "wsdl", 10 | "event": [ 11 | { 12 | "listen": "test", 13 | "script": { 14 | "id": "c6060120-659a-407f-a683-218ff79bcef0", 15 | "type": "text/javascript", 16 | "exec": [ 17 | "pm.test(\"Status code is 200\", function () {", 18 | " pm.response.to.have.status(200);", 19 | "});", 20 | "", 21 | "pm.test(\"Complex type 'ArrayOfProduct' was discovered\", function () {", 22 | " pm.expect(pm.response.text()).to.include(\"tns:ArrayOfProduct\");", 23 | "});", 24 | "" 25 | ] 26 | } 27 | } 28 | ], 29 | "request": { 30 | "auth": { 31 | "type": "noauth" 32 | }, 33 | "method": "GET", 34 | "header": [], 35 | "body": { 36 | "mode": "formdata", 37 | "formdata": [ 38 | { 39 | "key": "firstName", 40 | "value": "Adam", 41 | "type": "text" 42 | } 43 | ] 44 | }, 45 | "url": { 46 | "raw": "{{domain}}/zoap/demo/server?wsdl", 47 | "host": [ 48 | "{{domain}}" 49 | ], 50 | "path": [ 51 | "zoap", 52 | "demo", 53 | "server" 54 | ], 55 | "query": [ 56 | { 57 | "key": "wsdl", 58 | "value": null 59 | } 60 | ] 61 | } 62 | }, 63 | "response": [] 64 | }, 65 | { 66 | "name": "auth", 67 | "event": [ 68 | { 69 | "listen": "test", 70 | "script": { 71 | "id": "b4297775-2f9e-43bd-ace5-86404391d0f4", 72 | "type": "text/javascript", 73 | "exec": [ 74 | "pm.test(\"Status code is 200\", function () {", 75 | " pm.response.to.have.status(200);", 76 | "});", 77 | "" 78 | ] 79 | } 80 | } 81 | ], 82 | "request": { 83 | "method": "POST", 84 | "header": [ 85 | { 86 | "key": "Content-Type", 87 | "value": "application/xml" 88 | } 89 | ], 90 | "body": { 91 | "mode": "raw", 92 | "raw": "\n \n \n \n {{user}}\n {{password}}\n \n \n" 93 | }, 94 | "url": { 95 | "raw": "{{domain}}/zoap/demo/server", 96 | "host": [ 97 | "{{domain}}" 98 | ], 99 | "path": [ 100 | "zoap", 101 | "demo", 102 | "server" 103 | ] 104 | } 105 | }, 106 | "response": [] 107 | }, 108 | { 109 | "name": "ping", 110 | "event": [ 111 | { 112 | "listen": "test", 113 | "script": { 114 | "id": "8b8d5ac3-d83e-4f38-890b-abbcbe524b41", 115 | "type": "text/javascript", 116 | "exec": [ 117 | "pm.test(\"Body contains 'true'\", function () {", 118 | " pm.expect(pm.response.text()).to.include(\"true\");", 119 | "});" 120 | ] 121 | } 122 | } 123 | ], 124 | "request": { 125 | "method": "POST", 126 | "header": [ 127 | { 128 | "key": "Content-Type", 129 | "value": "application/xml" 130 | } 131 | ], 132 | "body": { 133 | "mode": "raw", 134 | "raw": "\r \r \r \r \t{{token}}\r \r \r" 135 | }, 136 | "url": { 137 | "raw": "{{domain}}/zoap/demo/server", 138 | "host": [ 139 | "{{domain}}" 140 | ], 141 | "path": [ 142 | "zoap", 143 | "demo", 144 | "server" 145 | ] 146 | } 147 | }, 148 | "response": [] 149 | }, 150 | { 151 | "name": "getProduct", 152 | "event": [ 153 | { 154 | "listen": "test", 155 | "script": { 156 | "id": "77b32064-9441-42af-a58e-35ce61963410", 157 | "type": "text/javascript", 158 | "exec": [ 159 | "pm.test(\"Status code is 200\", function () {", 160 | " pm.response.to.have.status(200);", 161 | "});", 162 | "" 163 | ] 164 | } 165 | } 166 | ], 167 | "request": { 168 | "method": "POST", 169 | "header": [ 170 | { 171 | "key": "Content-Type", 172 | "value": "application/xml" 173 | } 174 | ], 175 | "body": { 176 | "mode": "raw", 177 | "raw": "\n \n \n \n \t456\n {{token}}\n \n \n" 178 | }, 179 | "url": { 180 | "raw": "{{domain}}/zoap/demo/server", 181 | "host": [ 182 | "{{domain}}" 183 | ], 184 | "path": [ 185 | "zoap", 186 | "demo", 187 | "server" 188 | ] 189 | } 190 | }, 191 | "response": [] 192 | }, 193 | { 194 | "name": "getProducts", 195 | "event": [ 196 | { 197 | "listen": "test", 198 | "script": { 199 | "id": "93bdb6d4-4b10-4365-b304-208a03831dc6", 200 | "type": "text/javascript", 201 | "exec": [ 202 | "pm.test(\"Status code is 200\", function () {", 203 | " pm.response.to.have.status(200);", 204 | "});", 205 | "" 206 | ] 207 | } 208 | } 209 | ], 210 | "request": { 211 | "method": "POST", 212 | "header": [ 213 | { 214 | "key": "Content-Type", 215 | "value": "application/xml" 216 | } 217 | ], 218 | "body": { 219 | "mode": "raw", 220 | "raw": "\r \r \r \r \t\r \r category\r Outerwear\r \r \r {{token}}\r \r \r" 221 | }, 222 | "url": { 223 | "raw": "{{domain}}/zoap/demo/server", 224 | "host": [ 225 | "{{domain}}" 226 | ], 227 | "path": [ 228 | "zoap", 229 | "demo", 230 | "server" 231 | ] 232 | } 233 | }, 234 | "response": [] 235 | } 236 | ], 237 | "event": [ 238 | { 239 | "listen": "prerequest", 240 | "script": { 241 | "id": "3e54cfbb-cd79-4886-ace7-fefc587a7af7", 242 | "type": "text/javascript", 243 | "exec": [ 244 | "" 245 | ] 246 | } 247 | }, 248 | { 249 | "listen": "test", 250 | "script": { 251 | "id": "3643b0f5-ee8b-45b1-9381-5f781d4ac0d8", 252 | "type": "text/javascript", 253 | "exec": [ 254 | "" 255 | ] 256 | } 257 | } 258 | ], 259 | "variable": [ 260 | { 261 | "id": "095ec8cd-48c6-46ea-81ea-e2c9831f75e2", 262 | "key": "domain", 263 | "value": "http://example.com", 264 | "type": "string", 265 | "description": "" 266 | }, 267 | { 268 | "id": "8014ce93-723b-400f-86b9-f9d19f7c36ed", 269 | "key": "user", 270 | "value": "test@test.com", 271 | "type": "string", 272 | "description": "" 273 | }, 274 | { 275 | "id": "b39902dc-5edf-4e7a-8b74-7c7d285d30dc", 276 | "key": "password", 277 | "value": "tester", 278 | "type": "string", 279 | "description": "" 280 | }, 281 | { 282 | "id": "4f4496dd-5939-4b8f-b9c3-b1a4deb94608", 283 | "key": "token", 284 | "value": "tGSGYv8al1Ce6Rui8oa4Kjo8ADhYvR9x8KFZOeEGWgU1iscF7N2tUnI3t9bX", 285 | "type": "string", 286 | "description": "" 287 | } 288 | ] 289 | } --------------------------------------------------------------------------------