Passed
Push — master ( 517cb3...2c2725 )
by Atanas
02:15
created

RouteGroup   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 96
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 96
rs 10
c 0
b 0
f 0
ccs 0
cts 36
cp 0
wmc 14
lcom 2
cbo 4

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A getSatisfiedRoute() 0 9 3
A isSatisfied() 0 4 1
A handle() 0 4 2
A route() 0 12 3
A addMiddleware() 0 9 2
1
<?php
2
3
namespace WPEmerge\Routing;
4
5
use Closure;
6
use Exception;
7
use WPEmerge\Middleware\HasMiddlewareTrait;
8
use WPEmerge\Request;
9
use WPEmerge\Routing\Conditions\ConditionInterface;
10
use WPEmerge\Routing\Conditions\Url as UrlCondition;
11
12
class RouteGroup implements RouteInterface, HasRoutesInterface {
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
	 * @throws Exception
31
	 * @param  string|ConditionInterface $target
32
	 * @param  Closure                   $closure
33
	 */
34
	public function __construct( $target, Closure $closure ) {
35
		if ( is_string( $target ) ) {
36
			$target = new UrlCondition( $target );
37
		}
38
39
		if ( ! is_a( $target, UrlCondition::class ) ) {
40
			throw new Exception( 'Route groups can only use route strings.' );
41
		}
42
43
		$this->target = $target;
44
45
		$closure( $this );
46
	}
47
48
	/**
49
	 * Get the first child route which is satisfied
50
	 *
51
	 * @return RouteInterface|null
52
	 */
53
	protected function getSatisfiedRoute( Request $request ) {
54
		$routes = $this->getRoutes();
55
		foreach ( $routes as $route ) {
56
			if ( $route->isSatisfied( $request ) ) {
57
				return $route;
58
			}
59
		}
60
		return null;
61
	}
62
63
	/**
64
	 * {@inheritDoc}
65
	 */
66
	public function isSatisfied( Request $request ) {
67
		$route = $this->getSatisfiedRoute( $request );
68
		return $route !== null;
69
	}
70
71
	/**
72
	 * {@inheritDoc}
73
	 */
74
	public function handle( Request $request, $template ) {
75
		$route = $this->getSatisfiedRoute( $request );
76
		return $route ? $route->handle( $request, $template ) : null;
77
	}
78
79
	/**
80
	 * {@inheritDoc}
81
	 */
82
	public function route( $methods, $target, $handler ) {
83
		if ( is_string( $target ) ) {
84
			$target = new UrlCondition( $target );
85
		}
86
87
		if ( ! is_a( $target, UrlCondition::class ) ) {
88
			throw new Exception( 'Routes inside route groups can only use route strings.' );
89
		}
90
91
		$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 WPEmerge\Routing\Conditions\ConditionInterface as the method concatenate() does only exist in the following implementations of said interface: WPEmerge\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...
92
		return $this->traitRoute( $methods, $target, $handler );
93
	}
94
95
	/**
96
	 * {@inheritDoc}
97
	 */
98
	public function addMiddleware( $middleware ) {
99
		$routes = $this->getRoutes();
100
101
		foreach ( $routes as $route ) {
102
			$route->addMiddleware( $middleware );
103
		}
104
105
		return $this->traitAddMiddleware( $middleware );
106
	}
107
}
108