Passed
Pull Request — master (#275)
by
unknown
04:06
created

BrowserKit::convertFilesToSymfonyUploadedFiles()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.4746
c 0
b 0
f 0
cc 7
nc 11
nop 1
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, Behat\Symfony2Extension\Driver\KernelDriver.

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, Behat\Symfony2Extension\Driver\KernelDriver.

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 (!$client instanceof GoutteClient) {
66
            $tmpFiles = $this->convertFilesToSymfonyUploadedFiles($files);
67
        }
68
69
        $client->followRedirects(false);
70
        $client->request($method, $url, $parameters, $files, $headers, $content);
71
        $client->followRedirects(true);
72
        $this->resetHttpHeaders();
73
74
        foreach ($tmpFiles as $tmpName) {
75
            @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...
76
        }
77
78
        return $this->mink->getSession()->getPage();
79
    }
80
81
    public function setHttpHeader($name, $value)
82
    {
83
        $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, Behat\Symfony2Extension\Driver\KernelDriver.

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...
84
        // Goutte\Client
85
        if (method_exists($client, 'setHeader')) {
86
            $client->setHeader($name, $value);
87
        } else {
88
            // Symfony\Component\BrowserKit\Client
89
90
            /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
91
            $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
92
            $name = str_replace('-', '_', strtoupper($name));
93
94
            // CONTENT_* are not prefixed with HTTP_ in PHP when building $_SERVER
95
            if (!isset($contentHeaders[$name])) {
96
                $name = 'HTTP_' . $name;
97
            }
98
            /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
99
100
            $client->setServerParameter($name, $value);
101
        }
102
    }
103
104
    public function getHttpHeaders()
105
    {
106
        return array_change_key_case(
107
            $this->mink->getSession()->getResponseHeaders(),
108
            CASE_LOWER
109
        );
110
    }
111
112
    public function getHttpHeader($name)
113
    {
114
        $values = $this->getHttpRawHeader($name);
115
116
        return implode(', ', $values);
117
    }
118
119
    public function getHttpRawHeader($name)
120
    {
121
        $name = strtolower($name);
122
        $headers = $this->getHttpHeaders();
123
124
        if (isset($headers[$name])) {
125
            $value = $headers[$name];
126
            if (!is_array($headers[$name])) {
127
                $value = [$headers[$name]];
128
            }
129
        } else {
130
            throw new \OutOfBoundsException(
131
                "The header '$name' doesn't exist"
132
            );
133
        }
134
        return $value;
135
    }
136
137
    protected function resetHttpHeaders()
138
    {
139
        /** @var GoutteClient|BrowserKitClient $client */
140
        $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, Behat\Symfony2Extension\Driver\KernelDriver.

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...
141
142
        $client->setServerParameters([]);
143
        if ($client instanceof GoutteClient) {
144
            $client->restart();
145
        }
146
    }
147
148
    private function convertFilesToSymfonyUploadedFiles(& $files)
149
    {
150
        $tmpFiles = [];
151
        foreach ($files as $key => &$file) {
152
            $tmpName = false;
153
            if (is_string($file)) {
154
                $tmpName = tempnam(sys_get_temp_dir(), 'upload');
155
                copy($file, $tmpName);
156
                $tmpFiles[] = $tmpName;
157
                $originalName = $file;
158
            } elseif (is_array($file)) {
159
                // This mirrors Goutte\Client::addPostFiles() called from Goutte\Client::doRequest()
160
                // so that a Symfony\Component\HttpKernel\Client can have the same behaviour
161
                if (isset($file['tmp_name'])) {
162
                    $tmpName = $file['tmp_name'];
163
                    if (isset($file['name'])) {
164
                        $originalName = $file['name'];
165
                    } else {
166
                        $originalName = $tmpName;
167
                    }
168
                } else {
169
                    $subTmpFiles = $this->convertFilesToSymfonyUploadedFiles($file);
170
                    $tmpFiles = array_merge($tmpFiles, $subTmpFiles);
171
                }
172
            }
173
            if ($tmpName) {
174
                $file = new UploadedFile($tmpName, $originalName, null, null, true);
0 ignored issues
show
Bug introduced by
The variable $originalName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
175
            }
176
        }
177
178
        return $tmpFiles;
179
    }
180
}
181