Passed
Push — master ( 7916f9...af5dfc )
by Alexey
03:21
created

UrlGenerator   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 85
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 56.76%

Importance

Changes 0
Metric Value
wmc 9
lcom 1
cbo 5
dl 0
loc 85
ccs 21
cts 37
cp 0.5676
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A toCurrent() 0 12 2
A toRoute() 0 12 2
A buildRouteUri() 0 13 4
1
<?php declare(strict_types = 1);
2
3
namespace Venta\Routing;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
use Psr\Http\Message\UriInterface;
7
use Venta\Contracts\Http\Request;
8
use Venta\Contracts\Routing\Route as RouteContract;
9
use Venta\Contracts\Routing\RouteCollection as RouteCollectionContract;
10
use Venta\Contracts\Routing\UrlGenerator as UrlGeneratorContract;
11
use Venta\Routing\Exception\RouteNotFoundException;
12
13
/**
14
 * Class UrlGenerator
15
 *
16
 * @package Venta\Routing
17
 */
18
class UrlGenerator implements UrlGeneratorContract
19
{
20
    /**
21
     * @var ServerRequestInterface
22
     */
23
    private $request;
24
25
    /**
26
     * @var RouteCollection
27
     */
28
    private $routes;
29
30
    /**
31
     * @var UriInterface
32
     */
33
    private $uri;
34
35
    /**
36
     * UrlGenerator constructor.
37
     *
38
     * @param Request $request
39
     * @param RouteCollectionContract $routes
40
     * @param UriInterface $uri
41
     */
42 3
    public function __construct(Request $request, RouteCollectionContract $routes, UriInterface $uri)
43
    {
44 3
        $this->request = $request;
45 3
        $this->routes = $routes;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Documentation Bug introduced by
$routes is of type object<Venta\Contracts\Routing\RouteCollection>, but the property $routes was declared to be of type object<Venta\Routing\RouteCollection>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
46 3
        $this->uri = $uri;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
47 3
    }
48
49
    /**
50
     * @inheritDoc
51
     */
52 1
    public function toCurrent(array $variables = [], array $query = []): UriInterface
53
    {
54 1
        $route = $this->request->getRoute();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Http\Message\ServerRequestInterface as the method getRoute() does only exist in the following implementations of said interface: Venta\Http\Request.

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...
55
56 1
        if ($route === null) {
57
            throw new RouteNotFoundException(
58
                sprintf('Unable to generate an URL for current.')
59
            );
60
        }
61
62 1
        return $this->buildRouteUri($route, $variables, $query);
63
    }
64
65
    /**
66
     * @inheritDoc
67
     */
68 1
    public function toRoute(string $routeName, array $variables = [], array $query = []): UriInterface
69
    {
70 1
        $route = $this->routes->findByName($routeName);
71
72 1
        if ($route === null) {
73
            throw new RouteNotFoundException(
74
                sprintf('Unable to generate an URL for the named route "%s" as such route does not exist.', $routeName)
75
            );
76
        }
77
78 1
        return $this->buildRouteUri($route, $variables, $query);
79
    }
80
81
    /**
82
     * Builds URI for provided route instance.
83
     *
84
     * @param RouteContract $route
85
     * @param array $variables
86
     * @param array $query
87
     * @return UriInterface
88
     */
89 2
    private function buildRouteUri(RouteContract $route, array $variables = [], array $query = []): UriInterface
90
    {
91 2
        $uri = $this->uri
92 2
            ->withScheme($route->getScheme() ?: $this->request->getUri()->getScheme())
93 2
            ->withHost($route->getHost() ?: $this->request->getUri()->getHost())
94 2
            ->withPath($route->compilePath($variables));
95
96 2
        if ($query) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
97 2
            $uri = $uri->withQuery(http_build_query($query));
98
        }
99
100 2
        return $uri;
101
    }
102
}