ConnectionsHandler   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 97.3%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 13
dl 0
loc 382
ccs 144
cts 148
cp 0.973
rs 8.3999
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultConnectionsHandles() 0 4 1
A getAvailableConnections() 0 15 1
B getConnections() 0 57 7
A getConnection() 0 12 2
A isValidConnection() 0 7 1
A getActiveConnections() 0 6 1
B setActiveConnections() 0 22 4
C getCurrentConnection() 0 33 8
A getCurrentConnectionKey() 0 6 2
A setCurrentConnection() 0 18 3
A hasCurrentConnection() 0 4 1
A is() 0 4 1
A disconnect() 0 5 1
A getAvailableStages() 0 4 1
B setStage() 0 21 5
A isConnectionActive() 0 10 3
A unifyMultiserversDeclarations() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like ConnectionsHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConnectionsHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of Rocketeer
5
 *
6
 * (c) Maxime Fabre <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 */
12
13
namespace Rocketeer\Services\Connections;
14
15
use Illuminate\Support\Arr;
16
use Illuminate\Support\Collection;
17
use Rocketeer\Services\Connections\Connections\AbstractConnection;
18
use Rocketeer\Services\Connections\Connections\Connection;
19
use Rocketeer\Services\Connections\Connections\ConnectionInterface;
20
use Rocketeer\Services\Connections\Credentials\Keys\ConnectionKey;
21
use Rocketeer\Traits\ContainerAwareTrait;
22
23
/**
24
 * Handles, get and return, the various connections/stages
25
 * and their credentials.
26
 */
27
class ConnectionsHandler
28
{
29
    use ContainerAwareTrait;
30
31
    /**
32
     * @var Collection<ConnectionInterface>
33
     */
34
    protected $available;
35
36
    /**
37
     * @var string
38
     */
39
    protected $current;
40
41
    ////////////////////////////////////////////////////////////////////
42
    //////////////////////// AVAILABLE CONNECTIONS /////////////////////
43
    ////////////////////////////////////////////////////////////////////
44
45
    /**
46
     * @return string[]
47
     */
48 442
    public function getDefaultConnectionsHandles()
49
    {
50 442
        return (array) $this->config->get('default');
51
    }
52
53
    /**
54
     * Get the available connections.
55
     *
56
     * @return array
57
     */
58 442
    public function getAvailableConnections()
59
    {
60
        // Fetch stored credentials
61 442
        $storage = $this->localStorage->get('connections');
62 442
        $storage = $this->unifyMultiserversDeclarations($storage);
63
64
        // Merge with defaults from config file
65 442
        $configuration = $this->config->get('connections');
66 442
        $configuration = $this->unifyMultiserversDeclarations($configuration);
67
68
        // Merge configurations
69 442
        $connections = array_replace_recursive($configuration, $storage);
70
71 442
        return $connections;
72
    }
73
74
    /**
75
     * @return Collection<Connection>
0 ignored issues
show
Documentation introduced by Maxime Fabre
The doc-type Collection<Connection> could not be parsed: Expected "|" or "end of type", but got "<" at position 10. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
76
     */
77 442
    public function getConnections()
0 ignored issues
show
Documentation introduced by Maxime Fabre
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
78
    {
79
        // Convert to ConnectionKey/Connection instances
80 442
        if (!$this->available) {
81 442
            $connections = [];
82
83 442
            $available = $this->getAvailableConnections();
84 442
            foreach ($available as $name => $connection) {
85
                // Skip connections without any defined servers
86 328
                $servers = $connection['servers'];
87 328
                if (!$servers) {
88
                    continue;
89
                }
90
91 328
                foreach ($servers as $key => &$server) {
92 328
                    foreach ($server as $subkey => &$value) {
93 328
                        $option = $this->getOption($subkey, true);
94 328
                        $value = !is_null($option) ? $option : $value;
95 328
                    }
96 328
97
                    $connectionKey = new ConnectionKey([
98
                        'name' => $name,
99 328
                        'server' => $key,
100 328
                        'servers' => $servers,
101
                    ]);
102 328
103 328
                    // Create connection
104 442
                    $connection = $this->remote->make($connectionKey);
105
                    $connection->setActive($this->isConnectionActive($connection));
106
107 442
                    $connections[$connectionKey->toHandle()] = $connection;
108 442
                }
109 442
            }
110 442
111 442
            // Add local and dummy connections
112
            $connections['local'] = $this->remote->make(new ConnectionKey([
113 442
                'name' => 'local',
114 442
                'server' => 0,
115 442
                'servers' => [['host' => 'localhost']],
116
            ]));
117
118 442
            $connections['dummy'] = $this->remote->make(new ConnectionKey([
119 442
                'name' => 'dummy',
120 442
                'server' => 0,
121 442
                'servers' => [
122 442
                    [
123
                        'host' => 'localhost',
124 442
                        'root_directory' => '/tmp/rocketeer',
125 442
                    ],
126
                ],
127 442
            ]));
128
129
            $this->available = new Collection($connections);
130
        }
131
132
        return $this->available;
133
    }
134
135
    /**
136
     * @param ConnectionKey|string $connection
137
     * @param int|null             $server
138 7
     *
139
     * @throws ConnectionException
140 7
     *
141 7
     * @return Connection
142
     */
143 7
    public function getConnection($connection, $server = null)
144 7
    {
145
        $connectionKey = $this->credentials->sanitizeConnection($connection, $server);
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method sanitizeConnection does not exist on object<Rocketeer\Service...als\CredentialsHandler>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
146
        $handle = $connectionKey->toHandle();
147
148 7
        $connections = $this->getConnections();
149
        if (!$connections->has($handle)) {
150
            throw new ConnectionException('Invalid connection: '.$handle);
151
        }
152
153
        return $connections[$handle];
154
    }
155
156
    /**
157
     * Check if a connection has credentials related to it.
158 7
     *
159
     * @param ConnectionKey|string $connection
160 7
     *
161 7
     * @return bool
162
     */
163 7
    public function isValidConnection($connection)
164
    {
165
        $connection = $this->credentials->sanitizeConnection($connection);
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method sanitizeConnection does not exist on object<Rocketeer\Service...als\CredentialsHandler>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
166
        $available = (array) $this->getAvailableConnections();
167
168
        return (bool) Arr::get($available, $connection->name.'.servers');
169
    }
170
171
    ////////////////////////////////////////////////////////////////////////////////
172
    ////////////////////////////// ACTIVE CONNECTIONS //////////////////////////////
173
    ////////////////////////////////////////////////////////////////////////////////
174
175 442
    /**
176
     * Get the active connections for this session.
177
     *
178 442
     * @return Collection<Connection>
0 ignored issues
show
Documentation introduced by Maxime Fabre
The doc-type Collection<Connection> could not be parsed: Expected "|" or "end of type", but got "<" at position 10. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
179 442
     */
180
    public function getActiveConnections()
0 ignored issues
show
Documentation introduced by Maxime Fabre
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
181
    {
182
        return $this->getConnections()->filter(function (ConnectionInterface $connection) {
183
            return $this->isConnectionActive($connection);
184
        });
185
    }
186
187
    /**
188
     * Override the active connections.
189 7
     *
190
     * @param string|string[] $connections
191 7
     *
192 4
     * @throws ConnectionException
193 4
     */
194
    public function setActiveConnections($connections)
195
    {
196 7
        if (!is_array($connections)) {
197 7
            $connections = explode(',', $connections);
198 1
        }
199
200
        // Sanitize and set connections
201
        $filtered = array_filter($connections, [$this, 'isValidConnection']);
202 6
        if (!$filtered) {
203 6
            throw new ConnectionException('Invalid connection(s): '.implode(', ', $connections));
204
        }
205 6
206 6
        $this->available = $this->getConnections()->map(function (ConnectionInterface $connection) use ($connections) {
207
            $connectionKey = $connection->getConnectionKey();
208 6
            $handle = $connectionKey->toHandle();
209 6
210 6
            $connection->setActive(in_array($handle, $connections, true) || in_array($connectionKey->name, $connections, true));
211
            $connection->setCurrent(false);
212
213
            return $connection;
214
        });
215
    }
216
217
    ////////////////////////////////////////////////////////////////////////////////
218
    ///////////////////////////// CURRENT CONNECTIONS //////////////////////////////
219 442
    ////////////////////////////////////////////////////////////////////////////////
220
221 442
    /**
222 7
     * @return AbstractConnection
223
     */
224
    public function getCurrentConnection()
225 442
    {
226 442
        if ($this->rocketeer->isLocal()) {
227 270
            return $this->getConnection('local');
228
        }
229
230
        $connections = $this->getConnections();
231
        if ($this->current && $current = $connections->get($this->current)) {
232 442
            return $current;
233 442
        }
234
235
        /** @var ConnectionInterface $connection */
236 442
        $connection = $connections->first(function (ConnectionInterface $connection) {
237 442
            return $connection->isCurrent();
238 442
        });
239
240
        // If no connection is marked as current, get the first active one
241 442
        if (!$connection) {
242 442
            $connection = $this->getActiveConnections()->first();
243 296
        }
244 296
245 296
        // Fire connected event the first time
246
        $handle = $connection ? $connection->getConnectionKey()->toHandle() : null;
247
        if ($connection && !$connection->isConnected()) {
248 442
            $event = 'connected.'.$handle;
249
            $this->events->emit($event);
250 442
        }
251
252
        // Cache which connection is the first
253
        $this->current = $handle;
254
255
        return $connection;
256
    }
257
258 442
    /**
259
     * Get the current connection.
260 442
     *
261
     * @return ConnectionKey
262 442
     */
263
    public function getCurrentConnectionKey()
264
    {
265
        $current = $this->getCurrentConnection();
266
267
        return $current ? $current->getConnectionKey() : $this->credentials->createConnectionKey();
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method createConnectionKey does not exist on object<Rocketeer\Service...als\CredentialsHandler>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
268
    }
269
270
    /**
271 62
     * Set the current connection.
272
     *
273 62
     * @param ConnectionKey|string $connection
0 ignored issues
show
Documentation introduced by Maxime Fabre
Should the type for parameter $connection not be ConnectionKey|string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
274 62
     * @param int|null             $server
275 34
     */
276
    public function setCurrentConnection($connection = null, $server = null)
277
    {
278 61
        $connectionKey = $connection instanceof ConnectionKey ? $connection : $this->credentials->createConnectionKey($connection, $server);
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method createConnectionKey does not exist on object<Rocketeer\Service...als\CredentialsHandler>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
279
        if ($this->current === $connectionKey->toHandle()) {
280 61
            return;
281 61
        }
282
283 61
        $this->current = $connectionKey->toHandle();
284 61
        $this->available = $this->getConnections()->map(function (ConnectionInterface $connection) use ($connectionKey) {
285
            $isCurrent = $connectionKey->is($connection->getConnectionKey());
286
            $connection->setCurrent($isCurrent);
287 61
288 61
            return $connection;
289
        });
290
291
        // Update events
292
        $this->bootstrapper->bootstrapUserCode();
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method bootstrapUserCode does not exist on object<Rocketeer\Service...tstrapper\Bootstrapper>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
293
    }
294
295
    /**
296
     * @return bool
297
     */
298
    public function hasCurrentConnection()
299
    {
300
        return (bool) $this->getCurrentConnection();
301
    }
302
303
    /**
304 145
     * @param string   $name
305
     * @param int|null $server
306 145
     *
307
     * @return bool
308
     */
309
    public function is($name, $server = null)
310
    {
311
        return $this->getCurrentConnectionKey()->is($name, $server);
312 442
    }
313
314 442
    /**
315 442
     * Flush active connection(s).
316 442
     */
317
    public function disconnect()
318
    {
319
        $this->current = null;
320
        $this->available = [];
321
    }
322
323
    ////////////////////////////////////////////////////////////////////
324
    //////////////////////////////// STAGES ////////////////////////////
325
    ////////////////////////////////////////////////////////////////////
326
327 150
    /**
328
     * Get the various stages provided by the User.
329 150
     *
330
     * @return array
331
     */
332
    public function getAvailableStages()
333
    {
334
        return (array) $this->config->getContextually('stages.stages');
335
    }
336
337 14
    /**
338
     * Set the stage on the current connection.
339 14
     *
340 14
     * @param string|null $stage
341 3
     */
342
    public function setStage($stage)
343
    {
344 13
        $connectionKey = $this->getCurrentConnectionKey();
345 13
        if ($stage === $connectionKey->stage) {
346 13
            return;
347 13
        }
348 13
349
        $connectionKey->stage = $stage;
350 13
        $this->getConnections()->map(function (ConnectionInterface $connection) use ($connectionKey) {
351 13
            if ($connection->isCurrent() || $connection->getConnectionKey()->is($connectionKey)) {
352
                $connection->setConnectionKey($connectionKey);
353
            }
354 13
355 13
            return $connection;
356 13
        });
357 13
358
        // If we do have a stage, cleanup previous events
359
        if ($stage) {
360
            $this->bootstrapper->bootstrapUserCode();
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method bootstrapUserCode does not exist on object<Rocketeer\Service...tstrapper\Bootstrapper>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
361
        }
362
    }
363
364
    ////////////////////////////////////////////////////////////////////////////////
365
    /////////////////////////////////// HELPERS ////////////////////////////////////
366
    ////////////////////////////////////////////////////////////////////////////////
367
368 442
    /**
369
     * @param ConnectionInterface $connection
370 442
     *
371 442
     * @return bool
372
     */
373
    protected function isConnectionActive(ConnectionInterface $connection)
374 442
    {
375 442
        $connectionKey = $connection->getConnectionKey();
376 442
        $defaults = $this->getDefaultConnectionsHandles();
377
378
        return
379
            in_array($connectionKey->toHandle(), $defaults, true) ||
380
            in_array($connectionKey->name, $defaults, true) ||
381
            $connection->isActive();
382
    }
383
384
    /**
385
     * Unify a connection's declaration into the servers form.
386 442
     *
387
     * @param array $connection
388 442
     *
389 442
     * @return array
390 338
     */
391 338
    protected function unifyMultiserversDeclarations($connection)
392 338
    {
393 338
        $connection = (array) $connection;
394 6
        foreach ($connection as $key => $servers) {
395 6
            $servers = Arr::get($servers, 'servers', [$servers]);
396 338
            $servers = array_values($servers);
397
            foreach ($servers as &$server) {
398 338
                if (array_key_exists('key', $server)) {
399 442
                    $server['key'] = str_replace('~/', $this->paths->getUserHomeFolder().'/', $server['key']);
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method getUserHomeFolder does not exist on object<Rocketeer\Services\Environment\Pathfinder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
400
                }
401 442
            }
402
403
            $connection[$key] = ['servers' => $servers];
404
        }
405
406
        return $connection;
407
    }
408
}
409