Completed
Push — develop ( d80139...722dcc )
by Dieter
07:35 queued 01:02
created

Base::makeWsdlIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * amadeus-ws-client
4
 *
5
 * Copyright 2015 Amadeus Benelux NV
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 * http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 *
19
 * @package Amadeus
20
 * @license https://opensource.org/licenses/Apache-2.0 Apache 2.0
21
 */
22
23
namespace Amadeus\Client\Session\Handler;
24
25
use Amadeus\Client;
26
use Amadeus\Client\Struct\BaseWsMessage;
27
use Amadeus\Client\Params\SessionHandlerParams;
28
use Psr\Log\LoggerAwareInterface;
29
use Psr\Log\LoggerAwareTrait;
30
use Psr\Log\LoggerInterface;
31
use Psr\Log\LogLevel;
32
use Psr\Log\NullLogger;
33
34
/**
35
 * Base Session Handler
36
 *
37
 * Session handler will manage everything related to the session with the Amadeus Web Services server:
38
 * - be configurable to handle different versions of authentication mechanisms depending on the WSDL received
39
 * - decide if a separate authentication message is needed and if so, send it
40
 *
41
 * @package Amadeus\Client\Session\Handler
42
 * @author Dieter Devlieghere <[email protected]>
43
 */
44
abstract class Base implements HandlerInterface, LoggerAwareInterface
45
{
46
    use LoggerAwareTrait;
47
48
    /**
49
     * XPATH query to retrieve all operations from a WSDL
50
     *
51
     * @var string
52
     */
53
    const XPATH_ALL_OPERATIONS = '/wsdl:definitions/wsdl:portType/wsdl:operation/@name';
54
55
    /**
56
     * XPATH query to retrieve WSDL imports from a WSDL.
57
     */
58
    const XPATH_IMPORTS = '/wsdl:definitions/wsdl:import/@location';
59
60
    /**
61
     * XPATH query to retrieve the full operation name + version from a WSDL for a given operation.
62
     *
63
     * @var string
64
     */
65
    const XPATH_VERSION_FOR_OPERATION = "string(/wsdl:definitions/wsdl:message[contains(./@name, '%s_')]/@name)";
66
67
    const XPATH_ALT_VERSION_FOR_OPERATION = "string(//wsdl:operation[contains(./@name, '%s')]/wsdl:input/@message)";
68
69
70
    /**
71
     * Status variable to know wether the given session is in a certain context.
72
     *
73
     * @todo implement this feature - currently the application using the client must know the context itself.
74
     * @var bool
75
     */
76
    protected $hasContext = false;
77
78
    /**
79
     * The context of the currently active session
80
     *
81
     * @todo implement this feature - currently the application using the client must know the context itself.
82
     * @var mixed
83
     */
84
    protected $context;
85
86
    /**
87
     * Session information:
88
     * - session ID
89
     * - sequence number
90
     * - security Token
91
     *
92
     * @var array
93
     */
94
    protected $sessionData = [
95
        'sessionId' => null,
96
        'sequenceNumber' => null,
97
        'securityToken' => null
98
    ];
99
100
    /**
101
     * Status variable to know if the session is currently logged in
102
     *
103
     * @var bool
104
     */
105
    protected $isAuthenticated = false;
106
107
    /**
108
     * @var array[string]\SoapClient
109
     */
110
    protected $soapClients = [];
111
112
    /**
113
     * Default SoapClient options used during initialisation
114
     *
115
     * Can be overridden by providing custom options in
116
     * Amadeus\Client\Params\SessionHandlerParams::$soapClientOptions
117
     *
118
     * @var array
119
     */
120
    protected $soapClientOptions = [
121
        'trace' 		=> 1,
122
        'exceptions' 	=> 1,
123
        'soap_version' 	=> SOAP_1_1
124
    ];
125
126
    /**
127
     * @var SessionHandlerParams
128
     */
129
    protected $params;
130
131
    /**
132
     * Dom Document array where the WSDL's contents will be loaded
133
     *
134
     * format:
135
     * [
136
     *     '7d36c7b8' => \DOMDocument,
137
     *     '7e84f2537' => \DOMDocument
138
     * ]
139
     *
140
     * @var array
141
     */
142
    protected $wsdlDomDoc;
143
    /**
144
     * To query the WSDL contents
145
     *
146
     * format:
147
     * [
148
     *     '7d36c7b8' => \DOMXpath,
149
     *     '7e84f2537' => \DOMXpath
150
     * ]
151
     *
152
     * @var array
153
     */
154
    protected $wsdlDomXpath;
155
156
    /**
157
     * A map of which messages are available in the provided WSDL's
158
     *
159
     * format:
160
     * [
161
     *      'PNR_Retrieve' => [
162
     *          'version' => '14.1',
163
     *          'wsdl' => '7d36c7b8'
164
     *      ],
165
     *      'Media_GetMedia' => [
166
     *          'version' => '14.1',
167
     *          'wsdl' => '7e84f2537'
168
     *      ]
169
     * ]
170
     *
171
     * @var array
172
     */
173
    protected $messagesAndVersions = [];
174
175
    /**
176
     * List of WSDL ID's and their path
177
     *
178
     * format:
179
     * [
180
     *      '7d36c7b8' => '/path/to/wsdl.wsdl'
181
     * ]
182
     *
183
     * @var array
184
     */
185
    protected $wsdlIds = [];
186
187
188
    /**
189
     * Get the session parameters of the active session
190
     *
191
     * @return array|null
192
     */
193
    public function getSessionData()
194
    {
195
        return $this->sessionData;
196
    }
197
198
    /**
199
     * Set the session data to continue a previously started session.
200
     *
201
     * @param array $sessionData
202
     * @return bool
203
     */
204
    public function setSessionData(array $sessionData)
205
    {
206
        if (isset($sessionData['sessionId'], $sessionData['sequenceNumber'], $sessionData['securityToken'])) {
207
            $this->sessionData['sessionId'] = $sessionData['sessionId'];
208
            $this->sessionData['sequenceNumber'] = $sessionData['sequenceNumber'];
209
            $this->sessionData['securityToken'] = $sessionData['securityToken'];
210
            $this->isAuthenticated = true;
211
        } else {
212
            $this->isAuthenticated = false;
213
        }
214
215
        return $this->isAuthenticated;
216
    }
217
218
219
    /**
220
     * @param SessionHandlerParams $params
221
     */
222
    public function __construct(SessionHandlerParams $params)
223
    {
224
        $this->params = $params;
225
        if ($params->logger instanceof LoggerInterface) {
226
            $this->setLogger($params->logger);
227
            $this->log(LogLevel::INFO, __METHOD__."(): Logger started.");
228
        }
229
        if ($params->overrideSoapClient instanceof \SoapClient) {
230
            $this->soapClients[$params->overrideSoapClientWsdlName] = $params->overrideSoapClient;
231
        }
232
        $this->setStateful($params->stateful);
233
    }
234
235
236
    /**
237
     * @param string $messageName Method Operation name as defined in the WSDL.
238
     * @param BaseWsMessage $messageBody
239
     * @param array $messageOptions options: bool 'asString', bool 'endSession'
240
     * @return SendResult
241
     * @throws \InvalidArgumentException
242
     * @throws Client\Exception
243
     * @throws \SoapFault
244
     */
245
    public function sendMessage($messageName, Client\Struct\BaseWsMessage $messageBody, $messageOptions = [])
246
    {
247
        $result = new SendResult(
248
            $this->getActiveVersionFor($messageName)
249
        );
250
251
        $this->prepareForNextMessage($messageName, $messageOptions);
252
253
        try {
254
            $result->responseObject = $this->getSoapClient($messageName)->$messageName($messageBody);
255
256
            $this->logRequestAndResponse($messageName);
257
258
            $this->handlePostMessage($messageName, $this->getLastResponse($messageName), $messageOptions, $result);
259
260
        } catch (\SoapFault $ex) {
261
            $this->log(
262
                LogLevel::ERROR,
263
                "SOAPFAULT while sending message ".$messageName.": ".$ex->getMessage().
264
                " code: ".$ex->getCode()." at ".$ex->getFile()." line ".$ex->getLine().
265
                ": \n".$ex->getTraceAsString()
266
            );
267
            $this->logRequestAndResponse($messageName);
268
            $result->exception = $ex;
269
        } catch (\Exception $ex) {
270
            // We should only come here when the XSL extension is not enabled
271
            // or the XSLT transformation file is unreadable
272
            $this->log(
273
                LogLevel::ERROR,
274
                "EXCEPTION while sending message ".$messageName.": ".$ex->getMessage().
275
                " at ".$ex->getFile()." line ".$ex->getLine().": \n".$ex->getTraceAsString()
276
            );
277
            $this->logRequestAndResponse($messageName);
278
            throw new Client\Exception($ex->getMessage(), $ex->getCode(), $ex);
279
        }
280
281
        $result->responseXml = Client\Util\MsgBodyExtractor::extract($this->getLastResponse($messageName));
282
283
        return $result;
284
    }
285
286
    /**
287
     * Prepare to send a next message and checks if authenticated
288
     *
289
     * @param string $messageName
290
     * @param array $messageOptions
291
     */
292
    protected abstract function prepareForNextMessage($messageName, $messageOptions);
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
293
294
    /**
295
     * Handles post message actions
296
     *
297
     * Handles session state based on received response
298
     *
299
     * @param string $messageName
300
     * @param string $lastResponse
301
     * @param array $messageOptions
302
     * @param mixed $result
303
     */
304
    protected abstract function handlePostMessage($messageName, $lastResponse, $messageOptions, $result);
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
305
306
    /**
307
     * Get the last raw XML message that was sent out
308
     *
309
     * @param string $msgName
310
     * @return string|null
311
     */
312
    public function getLastRequest($msgName)
313
    {
314
        $lastRequest = null;
315
        $soapClient = $this->getSoapClient($msgName);
316
317
        if ($soapClient instanceof \SoapClient) {
318
            $lastRequest = $soapClient->__getLastRequest();
319
        }
320
321
        return $lastRequest;
322
    }
323
324
    /**
325
     * Get the last raw XML message that was received
326
     *
327
     * @param string $msgName
328
     * @return string|null
329
     */
330
    public function getLastResponse($msgName)
331
    {
332
        $lastResponse = null;
333
        $soapClient = $this->getSoapClient($msgName);
334
335
        if ($soapClient instanceof \SoapClient) {
336
            $lastResponse = $soapClient->__getLastResponse();
337
        }
338
339
        return $lastResponse;
340
    }
341
342
    /**
343
     * Get the office that we are using to sign in to.
344
     *
345
     * @return string
346
     */
347
    public function getOriginatorOffice()
348
    {
349
        return $this->params->authParams->officeId;
350
    }
351
352
353
    /**
354
     * Extract the Messages and versions from the loaded WSDL file.
355
     *
356
     * Result is an associative array: keys are message names, values are versions.
357
     *
358
     * @return array
359
     */
360
    public function getMessagesAndVersions()
361
    {
362
        if (empty($this->messagesAndVersions)) {
363
            $this->messagesAndVersions = $this->loadMessagesAndVersions();
364
        }
365
366
        return $this->messagesAndVersions;
367
    }
368
369
    /**
370
     * Loads messages & versions from WSDL.
371
     *
372
     * @return array
373
     */
374
    protected function loadMessagesAndVersions()
375
    {
376
        $msgAndVer = [];
377
378
        $wsdls = $this->params->wsdl;
379
380
        foreach ($wsdls as $wsdl) {
381
            $wsdlIdentifier = $this->makeWsdlIdentifier($wsdl);
382
383
            $this->wsdlIds[$wsdlIdentifier] = $wsdl;
384
385
            $this->loadWsdlXpath($wsdl, $wsdlIdentifier);
386
387
            $operations = $this->wsdlDomXpath[$wsdlIdentifier]->query(self::XPATH_ALL_OPERATIONS);
388
            if ($operations->length === 0) {
389
                //No operations found - are there any external WSDLs being imported?
390
                $imports = $this->wsdlDomXpath[$wsdlIdentifier]->query(self::XPATH_IMPORTS);
391
                $operations = [];
392
393
                foreach ($imports as $import) {
394
                    if (!empty($import->value)) {
395
                        $tmpMsg = $this->getMessagesAndVersionsFromImportedWsdl(
396
                            $import->value,
397
                            $wsdl,
398
                            $wsdlIdentifier
399
                        );
400
                        foreach ($tmpMsg as $msgName=>$msgInfo) {
401
                            $msgAndVer[$msgName] = $msgInfo;
402
                        }
403
                    }
404
                }
405
            }
406
407 View Code Duplication
            foreach ($operations as $operation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
                if (!empty($operation->value)) {
409
                    $fullVersion = $this->wsdlDomXpath[$wsdlIdentifier]->evaluate(
410
                        sprintf(self::XPATH_VERSION_FOR_OPERATION, $operation->value)
411
                    );
412
413
                    if (!empty($fullVersion)) {
414
                        $extractedVersion = $this->extractMessageVersion($fullVersion);
415
                        $msgAndVer[$operation->value] = [
416
                            'version' => $extractedVersion,
417
                            'wsdl' => $wsdlIdentifier
418
                        ];
419
                    }
420
                }
421
            }
422
        }
423
424
        return $msgAndVer;
425
    }
426
427
    /**
428
     * Generates a unique identifier for a wsdl based on its path.
429
     *
430
     * @param string $wsdlPath
431
     *
432
     * @return string
433
     */
434
    protected function makeWsdlIdentifier($wsdlPath)
435
    {
436
        return sprintf('%x', crc32($wsdlPath));
437
    }
438
439
    /**
440
     * extractMessageVersion
441
     *
442
     * extracts "4.1" from a string like "Security_SignOut_4_1"
443
     * or "1.00" from a string like "tns:AMA_MediaGetMediaRQ_1.000"
444
     *
445
     * @param string $fullVersionString
446
     * @return string
447
     */
448
    protected function extractMessageVersion($fullVersionString)
449
    {
450
        $marker = strpos($fullVersionString, '_', strpos($fullVersionString, '_')+1);
451
452
        $num = substr($fullVersionString, $marker+1);
453
454
        return str_replace('_', '.', $num);
455
    }
456
457
    /**
458
     * Load the WSDL contents to a queryable DOMXpath.
459
     *
460
     * @param string $wsdlFilePath
461
     * @param string $wsdlId
462
     * @uses $this->wsdlDomDoc
463
     * @uses $this->wsdlDomXpath
464
     */
465
    protected function loadWsdlXpath($wsdlFilePath, $wsdlId)
466
    {
467
        if (!isset($this->wsdlDomXpath[$wsdlId]) || is_null($this->wsdlDomXpath[$wsdlId])) {
468
            $wsdlContent = file_get_contents($wsdlFilePath);
469
470
            if ($wsdlContent !== false) {
471
                $this->wsdlDomDoc[$wsdlId] = new \DOMDocument('1.0', 'UTF-8');
472
                $this->wsdlDomDoc[$wsdlId]->loadXML($wsdlContent);
473
                $this->wsdlDomXpath[$wsdlId] = new \DOMXPath($this->wsdlDomDoc[$wsdlId]);
474
                $this->wsdlDomXpath[$wsdlId]->registerNamespace(
475
                    'wsdl',
476
                    'http://schemas.xmlsoap.org/wsdl/'
477
                );
478
                $this->wsdlDomXpath[$wsdlId]->registerNamespace(
479
                    'soap',
480
                    'http://schemas.xmlsoap.org/wsdl/soap/'
481
                );
482
            } else {
483
                throw new Client\Exception('WSDL ' . $wsdlFilePath . ' could not be loaded');
484
            }
485
        }
486
    }
487
488
489
490
    /**
491
     * Get the version number active in the WSDL for the given message
492
     *
493
     * @param $messageName
494
     * @return float|string|null
495
     */
496
    protected function getActiveVersionFor($messageName)
497
    {
498
        $msgAndVer = $this->getMessagesAndVersions();
499
500
        $found = null;
501
502
        if (isset($msgAndVer[$messageName]) && isset($msgAndVer[$messageName]['version'])) {
503
            $found = $msgAndVer[$messageName]['version'];
504
        }
505
506
        return $found;
507
    }
508
509
    /**
510
     * Get the WSDL ID for the given message
511
     *
512
     * @param $messageName
513
     * @return string|null
514
     */
515
    protected function getWsdlIdFor($messageName)
516
    {
517
        $msgAndVer = $this->getMessagesAndVersions();
518
519
        if (isset($msgAndVer[$messageName]) && isset($msgAndVer[$messageName]['wsdl'])) {
520
            return $msgAndVer[$messageName]['wsdl'];
521
        }
522
523
        return null;
524
    }
525
526
527
    /**
528
     * @param string $msgName
529
     * @return \SoapClient
530
     */
531
    protected function getSoapClient($msgName)
532
    {
533
        $wsdlId = $this->getWsdlIdFor($msgName);
534
535
        if (!empty($msgName)) {
536
            if (!isset($this->soapClients[$wsdlId]) || !($this->soapClients[$wsdlId] instanceof \SoapClient)) {
537
                $this->soapClients[$wsdlId] = $this->initSoapClient($wsdlId);
538
            }
539
540
            return $this->soapClients[$wsdlId];
541
        } else {
542
            return null;
543
        }
544
    }
545
546
    /**
547
     * @param string $wsdlId
548
     * @return \SoapClient
549
     */
550
    protected abstract function initSoapClient($wsdlId);
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
551
552
    /**
553
     * Get Messages & Versions from an imported WSDL file
554
     *
555
     * Imported wsdl's are a little different, they require a different query
556
     * to extract the version nrs.
557
     *
558
     * @param string $import
559
     * @param string $wsdlPath
560
     * @param string $wsdlIdentifier
561
     * @return array
562
     * @throws Client\Exception when the WSDL import could not be loaded.
563
     */
564
    protected function getMessagesAndVersionsFromImportedWsdl($import, $wsdlPath, $wsdlIdentifier)
565
    {
566
        $msgAndVer = [];
567
        $domDoc = null;
0 ignored issues
show
Unused Code introduced by
$domDoc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
568
        $domXpath = null;
569
570
        $importPath = realpath(dirname($wsdlPath)) . DIRECTORY_SEPARATOR . $import;
571
        $wsdlContent = file_get_contents($importPath);
572
573
        if ($wsdlContent !== false) {
574
            $domDoc = new \DOMDocument('1.0', 'UTF-8');
575
            $ok = $domDoc->loadXML($wsdlContent);
576
577
            if ($ok === true) {
578
                $domXpath = new \DOMXPath($domDoc);
579
                $domXpath->registerNamespace(
580
                    'wsdl',
581
                    'http://schemas.xmlsoap.org/wsdl/'
582
                );
583
                $domXpath->registerNamespace(
584
                    'soap',
585
                    'http://schemas.xmlsoap.org/wsdl/soap/'
586
                );
587
            }
588
        } else {
589
            throw new Client\Exception('WSDL ' . $importPath . ' import could not be loaded');
590
        }
591
592
        if ($domXpath instanceof \DOMXPath) {
593
            $nodeList = $domXpath->query(self::XPATH_ALL_OPERATIONS);
594
595 View Code Duplication
            foreach ($nodeList as $operation) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
596
                if (!empty($operation->value)) {
597
                    $fullVersion = $domXpath->evaluate(
598
                        sprintf(self::XPATH_ALT_VERSION_FOR_OPERATION, $operation->value)
599
                    );
600
601
                    if (!empty($fullVersion)) {
602
                        $extractedVersion = $this->extractMessageVersion($fullVersion);
603
                        $msgAndVer[$operation->value] = [
604
                            'version' => $extractedVersion,
605
                            'wsdl' => $wsdlIdentifier
606
                        ];
607
                    }
608
                }
609
            }
610
        }
611
612
        return $msgAndVer;
613
    }
614
615
    /**
616
     * @param string $messageName
617
     * @uses $this->log
618
     */
619
    protected function logRequestAndResponse($messageName)
620
    {
621
        $this->log(
622
            LogLevel::INFO,
623
            'Called ' . $messageName . ' with request: ' . $this->getSoapClient($messageName)->__getLastRequest()
624
        );
625
        $this->log(
626
            LogLevel::INFO,
627
            'Response:  ' . $this->getSoapClient($messageName)->__getLastResponse()
628
        );
629
    }
630
631
    /**
632
     * @param mixed $level
633
     * @param string $message
634
     * @param array $context
635
     * @return null
636
     */
637
    protected function log($level, $message, $context = [])
638
    {
639
        if (is_null($this->logger)) {
640
            $this->setLogger(new NullLogger());
641
        }
642
643
        return $this->logger->log($level, $message, $context);
644
    }
645
}
646