├── 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 | [](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 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
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 | }
--------------------------------------------------------------------------------