Passed
Push — master ( b7467b...db04eb )
by Atanas
02:41
created

RouteGroup::addMiddleware()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
ccs 0
cts 6
cp 0
crap 6
1
<?php
2
3
namespace Obsidian\Routing;
4
5
use Obsidian\Middleware\HasMiddlewareTrait;
6
use Obsidian\Request;
7
use Obsidian\Routing\Conditions\ConditionInterface;
8
use Obsidian\Routing\Conditions\Url as UrlCondition;
9
use Closure;
10
use Exception;
11
12
class RouteGroup implements RouteInterface {
13
	use HasRoutesTrait {
14
		route as traitRoute;
15
	}
16
	use HasMiddlewareTrait {
17
		addMiddleware as traitAddMiddleware;
18
	}
19
20
	/**
21
	 * Route target
22
	 *
23
	 * @var ConditionInterface
24
	 */
25
	protected $target = null;
26
27
	/**
28
	 * Constructor
29
	 *
30
	 * @param string|ConditionInterface $target
31
	 * @param Closure                   $callable
32
	 */
33
	public function __construct( $target, Closure $callable ) {
34
		if ( is_string( $target ) ) {
35
			$target = new UrlCondition( $target );
36
		}
37
38
		if ( ! is_a( $target, UrlCondition::class ) ) {
39
			throw new Exception( 'Route groups can only use route strings.' );
40
		}
41
42
		$this->target = $target;
43
44
		$callable( $this );
45
	}
46
47
	/**
48
	 * Return the first child route which is satisfied
49
	 *
50
	 * @return RouteInterface|null
51
	 */
52
	protected function getSatisfiedRoute( Request $request ) {
53
		$routes = $this->getRoutes();
54
		foreach ( $routes as $route ) {
55
			if ( $route->satisfied( $request ) ) {
56
				return $route;
57
			}
58
		}
59
		return null;
60
	}
61
62
	/**
63
	 * {@inheritDoc}
64
	 */
65
	public function satisfied( Request $request ) {
66
		$route = $this->getSatisfiedRoute( $request );
67
		return $route !== null;
68
	}
69
70
	/**
71
	 * {@inheritDoc}
72
	 */
73
	public function handle( Request $request, $template ) {
74
		$route = $this->getSatisfiedRoute( $request );
75
		return $route ? $route->handle( $request, $template ) : null;
76
	}
77
78
	/**
79
	 * {@inheritDoc}
80
	 */
81
	public function route( $methods, $target, $handler ) {
82
		if ( is_string( $target ) ) {
83
			$target = new UrlCondition( $target );
84
		}
85
86
		if ( ! is_a( $target, UrlCondition::class ) ) {
87
			throw new Exception( 'Routes inside route groups can only use route strings.' );
88
		}
89
90
		$target = $this->target->concatenate( $target );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Obsidian\Routing\Conditions\ConditionInterface as the method concatenate() does only exist in the following implementations of said interface: Obsidian\Routing\Conditions\Url.

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
		return $this->traitRoute( $methods, $target, $handler );
92
	}
93
94
	/**
95
	 * {@inheritDoc}
96
	 */
97
	public function addMiddleware( $middleware ) {
98
		$routes = $this->getRoutes();
99
100
		foreach ( $routes as $route ) {
101
			$route->addMiddleware( $middleware );
102
		}
103
104
		return $this->traitAddMiddleware( $middleware );
105
	}
106
}
107