Completed
Push — master ( 296066...80fa1f )
by James
01:29
created

SoapClient::buildHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 1
1
<?php
2
/**
3
 * Contains \JamesIArmes\PhpNtlm\NTLMSoapClient.
4
 */
5
6
namespace jamesiarmes\PhpNtlm;
7
8
/**
9
 * Soap Client using Microsoft's NTLM Authentication.
10
 *
11
 * @package php-ntlm\Soap
12
 */
13
class SoapClient extends \SoapClient
14
{
15
    /**
16
     * cURL resource used to make the SOAP request
17
     *
18
     * @var resource
19
     */
20
    protected $ch;
21
22
    /**
23
     * Options passed to the client constructor.
24
     *
25
     * @var array
26
     */
27
    protected $options;
28
29
    /**
30
     * {@inheritdoc}
31
     */
32
    public function __construct($wsdl, array $options = null)
33
    {
34
        // Set missing indexes to their default value.
35
        $options += array(
36
            'user' => null,
37
            'password' => null,
38
            'curlopts' => array(),
39
        );
40
        $this->options = $options;
41
42
        // Verify that a user name and password were entered.
43
        if (empty($options['user']) || empty($options['password'])) {
44
            throw new \BadMethodCallException(
45
                'A username and password is required.'
46
            );
47
        }
48
49
        parent::__construct($wsdl, $options);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapClient as the method __construct() does only exist in the following sub-classes of SoapClient: jamesiarmes\PhpNtlm\SoapClient. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
56
    {
57
        $headers = $this->buildHeaders($action);
58
        $this->__last_request_headers = $headers;
0 ignored issues
show
Bug introduced by
The property __last_request_headers does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
59
60
        // Only reinitialize curl handle if the location is different.
61
        if (!$this->ch
62
            || curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL) != $location) {
63
            $this->ch = curl_init($location);
64
        }
65
66
        curl_setopt_array($this->ch, $this->curlOptions($action, $request));
67
        $response = curl_exec($this->ch);
68
69
        // TODO: Add some real error handling.
70
        // If the response if false than there was an error and we should throw
71
        // an exception.
72
        if ($response === false) {
73
            throw new \RuntimeException(
74
                'Curl error: ' . curl_error($this->ch),
75
                curl_errno($this->ch)
76
            );
77
        }
78
79
        return $response;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function __getLastRequestHeaders()
86
    {
87
        return implode('n', $this->__last_request_headers) . "\n";
88
    }
89
90
    /**
91
     * Returns the response code from the last request
92
     *
93
     * @return integer
94
     *
95
     * @throws \BadMethodCallException
96
     *   If no cURL resource has been initialized.
97
     */
98
    public function getResponseCode()
99
    {
100
        if (empty($this->ch)) {
101
            throw new \BadMethodCallException('No cURL resource has been '
102
                . 'initialized. This is probably because no request has not '
103
                . 'been made.');
104
        }
105
106
        return curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
107
    }
108
109
    /**
110
     * Builds the headers for the request.
111
     *
112
     * @param string $action
113
     *   The SOAP action to be performed.
114
     */
115
    protected function buildHeaders($action)
116
    {
117
        return array(
118
            'Method: POST',
119
            'Connection: Keep-Alive',
120
            'User-Agent: PHP-SOAP-CURL',
121
            'Content-Type: text/xml; charset=utf-8',
122
            "SOAPAction: \"$action\"",
123
            'Expect: 100-continue',
124
        );
125
    }
126
127
    /**
128
     * Builds an array of curl options for the request
129
     *
130
     * @param string $action
131
     *   The SOAP action to be performed.
132
     * @param string $request
133
     *   The XML SOAP request.
134
     * @return array
135
     *   Array of curl options.
136
     */
137
    protected function curlOptions($action, $request)
138
    {
139
        $options = $this->options['curlopts'] + array(
140
            CURLOPT_SSL_VERIFYPEER => true,
141
            CURLOPT_RETURNTRANSFER => true,
142
            CURLOPT_HTTPHEADER => $this->buildHeaders($action),
143
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
144
            CURLOPT_HTTPAUTH => CURLAUTH_BASIC | CURLAUTH_NTLM,
145
            CURLOPT_USERPWD => $this->options['user'] . ':'
146
                               . $this->options['password'],
147
        );
148
149
        // We shouldn't allow these options to be overridden.
150
        $options[CURLOPT_POST] = true;
151
        $options[CURLOPT_POSTFIELDS] = $request;
152
153
        return $options;
154
    }
155
}
156