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

Route   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 87
rs 10
c 0
b 0
f 0
ccs 0
cts 28
cp 0
wmc 8
lcom 1
cbo 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A satisfied() 0 6 2
A handle() 0 6 1
A rewrite() 0 15 2
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\Factory;
9
use Obsidian\Routing\Conditions\InvalidRouteConditionException;
10
use Obsidian\Routing\Conditions\Url as UrlCondition;
11
use Exception;
12
13
/**
14
 * Represent a route
15
 */
16
class Route implements RouteInterface {
17
	use HasMiddlewareTrait;
18
19
	/**
20
	 * Allowed methods
21
	 *
22
	 * @var string[]
23
	 */
24
	protected $methods = [];
25
26
	/**
27
	 * Route target
28
	 *
29
	 * @var ConditionInterface
30
	 */
31
	protected $target = null;
32
33
	/**
34
	 * Route handler
35
	 *
36
	 * @var Handler|null
37
	 */
38
	protected $handler = null;
39
40
	/**
41
	 * Constructor
42
	 *
43
	 * @param string[]        $methods
44
	 * @param mixed           $target
45
	 * @param string|\Closure $handler
46
	 */
47
	public function __construct( $methods, $target, $handler ) {
48
		if ( ! is_a( $target, ConditionInterface::class ) ) {
49
			try {
50
				$target = Factory::make( $target );
51
			} catch ( InvalidRouteConditionException $e ) {
52
				throw new Exception( 'Route target is not a valid route string or condition.' );
53
			}
54
		}
55
56
		$this->methods = $methods;
57
		$this->target = $target;
58
		$this->handler = new Handler( $handler );
59
	}
60
61
	/**
62
	 * {@inheritDoc}
63
	 */
64
	public function satisfied( Request $request ) {
65
		if ( ! in_array( $request->getMethod(), $this->methods) ) {
66
			return false;
67
		}
68
		return $this->target->satisfied( $request );
69
	}
70
71
	/**
72
	 * {@inheritDoc}
73
	 */
74
	public function handle( Request $request, $template ) {
75
		$arguments = array_merge( [$request, $template], $this->target->getArguments( $request ) );
76
		return $this->executeMiddleware( $this->getMiddleware(), $request, function() use ( $arguments ) {
77
			return call_user_func_array( [$this->handler, 'execute'], $arguments );
78
		} );
79
	}
80
81
	/**
82
	 * Add a rewrite rule to WordPress for url-based routes
83
	 *
84
	 * @param  string $rewrite_to
85
	 * @return RouteInterface
86
	 */
87
	public function rewrite( $rewrite_to ) {
88
		if ( ! is_a( $this->target, UrlCondition::class ) ) {
89
			throw new Exception( 'Only routes with url targets can add rewrite rules.' );
90
		}
91
92
		$regex = $this->target->getValidationRegex( $this->target->getUrl(), false );
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 getUrl() 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...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Obsidian\Routing\Conditions\ConditionInterface as the method getValidationRegex() 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...
93
		$regex = preg_replace( '~^\^/~', '^', $regex ); // rewrite rules require NO leading slash
94
95
		add_filter( 'obsidian_routing_rewrite_rules', function( $rules ) use ( $regex, $rewrite_to ) {
96
			$rules[ $regex ] = $rewrite_to;
97
			return $rules;
98
		} );
99
100
		return $this;
101
	}
102
}
103