Passed
Pull Request — master (#275)
by
unknown
02:11
created

BrowserKit::send()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8657
c 0
b 0
f 0
cc 6
nc 4
nop 6
1
<?php
2
3
namespace Behatch\HttpCall\Request;
4
5
use Behat\Mink\Driver\Goutte\Client as GoutteClient;
6
use Behat\Mink\Mink;
7
use Symfony\Component\BrowserKit\Client as BrowserKitClient;
8
use Symfony\Component\HttpFoundation\File\UploadedFile;
9
10
class BrowserKit
11
{
12
    protected $mink;
13
14
    public function __construct(Mink $mink)
15
    {
16
        $this->mink = $mink;
17
    }
18
19
    public function getMethod()
20
    {
21
        return $this->getRequest()
22
            ->getMethod();
23
    }
24
25
    public function getUri()
26
    {
27
        return $this->getRequest()
28
            ->getUri();
29
    }
30
31
    public function getServer()
32
    {
33
        return $this->getRequest()
34
            ->getServer();
35
    }
36
37
    public function getParameters()
38
    {
39
        return $this->getRequest()
40
            ->getParameters();
41
    }
42
43
    protected function getRequest()
44
    {
45
        $client = $this->mink->getSession()->getDriver()->getClient();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getClient() does only exist in the following implementations of said interface: Behat\Mink\Driver\BrowserKitDriver, Behat\Mink\Driver\GoutteDriver.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
46
        // BC layer for BrowserKit 2.2.x and older
47
        if (method_exists($client, 'getInternalRequest')) {
48
            $request = $client->getInternalRequest();
49
        } else {
50
            $request = $client->getRequest();
51
        }
52
        return $request;
53
    }
54
55
    public function getContent()
56
    {
57
        return $this->mink->getSession()->getPage()->getContent();
58
    }
59
60
    public function send($method, $url, $parameters = [], $files = [], $content = null, $headers = [])
61
    {
62
        $client = $this->mink->getSession()->getDriver()->getClient();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getClient() does only exist in the following implementations of said interface: Behat\Mink\Driver\BrowserKitDriver, Behat\Mink\Driver\GoutteDriver.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
63
64
        $tmpFiles = [];
65
        if (true || !$client instanceof GoutteClient) {
66
            foreach ($files as &$file) {
67
                if (is_string($file)) {
68
                    $tmpName = tempnam(sys_get_temp_dir(), 'upload');
69
                    copy($file, $tmpName);
70
                    $tmpFiles[] = $tmpName;
71
                    $file = new UploadedFile($tmpName, $file, null, null, true);
72
                }
73
            }
74
        }
75
76
        $client->followRedirects(false);
77
        $client->request($method, $url, $parameters, $files, $headers, $content);
78
        $client->followRedirects(true);
79
        $this->resetHttpHeaders();
80
81
        foreach ($tmpFiles as $tmpName) {
82
            @unlink($tmpName);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
83
        }
84
85
        return $this->mink->getSession()->getPage();
86
    }
87
88
    public function setHttpHeader($name, $value)
89
    {
90
        $client = $this->mink->getSession()->getDriver()->getClient();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getClient() does only exist in the following implementations of said interface: Behat\Mink\Driver\BrowserKitDriver, Behat\Mink\Driver\GoutteDriver.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
91
        // Goutte\Client
92
        if (method_exists($client, 'setHeader')) {
93
            $client->setHeader($name, $value);
94
        } else {
95
            // Symfony\Component\BrowserKit\Client
96
97
            /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
98
            $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
99
            $name = str_replace('-', '_', strtoupper($name));
100
101
            // CONTENT_* are not prefixed with HTTP_ in PHP when building $_SERVER
102
            if (!isset($contentHeaders[$name])) {
103
                $name = 'HTTP_' . $name;
104
            }
105
            /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
106
107
            $client->setServerParameter($name, $value);
108
        }
109
    }
110
111
    public function getHttpHeaders()
112
    {
113
        return array_change_key_case(
114
            $this->mink->getSession()->getResponseHeaders(),
115
            CASE_LOWER
116
        );
117
    }
118
119
    public function getHttpHeader($name)
120
    {
121
        $values = $this->getHttpRawHeader($name);
122
123
        return implode(', ', $values);
124
    }
125
126
    public function getHttpRawHeader($name)
127
    {
128
        $name = strtolower($name);
129
        $headers = $this->getHttpHeaders();
130
131
        if (isset($headers[$name])) {
132
            $value = $headers[$name];
133
            if (!is_array($headers[$name])) {
134
                $value = [$headers[$name]];
135
            }
136
        } else {
137
            throw new \OutOfBoundsException(
138
                "The header '$name' doesn't exist"
139
            );
140
        }
141
        return $value;
142
    }
143
144
    protected function resetHttpHeaders()
145
    {
146
        /** @var GoutteClient|BrowserKitClient $client */
147
        $client = $this->mink->getSession()->getDriver()->getClient();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Mink\Driver\DriverInterface as the method getClient() does only exist in the following implementations of said interface: Behat\Mink\Driver\BrowserKitDriver, Behat\Mink\Driver\GoutteDriver.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
148
149
        $client->setServerParameters([]);
150
        if ($client instanceof GoutteClient) {
151
            $client->restart();
152
        }
153
    }
154
}
155