Passed
Push — master ( 27c065...381e9f )
by Atanas
01:52
created

Factory::makeFromArrayOfConditions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace Obsidian\Routing\Conditions;
4
5
use Obsidian\Framework;
6
use Obsidian\Routing\Conditions\Custom as CustomCondition;
7
use Obsidian\Routing\Conditions\Multiple as MultipleCondition;
8
use Obsidian\Routing\Conditions\Url as UrlCondition;
9
use Closure;
10
use Exception;
11
use ReflectionClass;
12
13
/**
14
 * Check against the current url
15
 */
16
class Factory {
17
	/**
18
	 * Create a new condition
19
	 *
20
	 * @param  string|array|Closure $options
21
	 * @return ConditionInterface
22
	 */
23 12
	public static function make( $options ) {
24 12
		if ( is_string( $options ) ) {
25 2
			return static::makeFromUrl( $options );
26
		}
27
28 10
		if ( is_array( $options ) ) {
29 8
			return static::makeFromArray( $options );
30
		}
31
32 2
		if ( is_a( $options, Closure::class ) ) {
33 1
			return static::makeFromClosure( $options );
34
		}
35
36 1
		if ( is_a( $options, ConditionInterface::class ) ) {
37
			return $options;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $options; (Closure) is incompatible with the return type documented by Obsidian\Routing\Conditions\Factory::make of type Obsidian\Routing\Conditions\ConditionInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
38
		}
39
40 1
		throw new InvalidRouteConditionException( 'Invalid condition options supplied.' );
41
	}
42
43
	/**
44
	 * Check if the passed argument is a registered condition type
45
	 *
46
	 * @param  mixed   $condition_type
47
	 * @return boolean
48
	 */
49 6
	protected static function conditionTypeRegistered( $condition_type ) {
50 6
		if ( ! is_string( $condition_type ) ) {
51 1
			return false;
52
		}
53
54 5
		$condition_class = Framework::resolve( 'framework.routing.conditions.' . $condition_type );
55 5
		return ( $condition_class !== null );
56
	}
57
58
	/**
59
	 * Resolve the condition type and its arguments from an options array
60
	 *
61
	 * @param  array $options
62
	 * @return array
63
	 */
64 6
	protected static function getConditionTypeAndArguments( $options ) {
65 6
		$type = $options[0];
66 6
		$arguments = array_slice( $options, 1 );
67
68 6
		if ( ! static::conditionTypeRegistered( $type ) ) {
69 3
			if ( is_callable( $type ) ) {
70 2
				$type = 'custom';
71 2
				$arguments = $options;
72
			} else {
73 1
				throw new Exception( 'Unknown condition type specified: ' . $type );
74
			}
75
		}
76
77
		return array(
78 5
			'type' => $type,
79 5
			'arguments' => $arguments,
80
		);
81
	}
82
83
	/**
84
	 * Create a new condition from a url
85
	 *
86
	 * @param  string             $url
87
	 * @return ConditionInterface
88
	 */
89 1
	protected static function makeFromUrl( $url ) {
90 1
		return new UrlCondition( $url );
91
	}
92
93
	/**
94
	 * Create a new condition from an array of conditions
95
	 *
96
	 * @param  array               $options
97
	 * @return ConditionInterface
98
	 */
99 1
	protected static function makeFromArrayOfConditions( $options ) {
100 1
		return new MultipleCondition( $options );
101
	}
102
103
	/**
104
	 * Create a new condition from an array
105
	 *
106
	 * @param  array               $options
107
	 * @return ConditionInterface
108
	 */
109 8
	protected static function makeFromArray( $options ) {
110 8
		if ( count( $options ) === 0 ) {
111 1
			throw new Exception( 'No condition type specified.' );
112
		}
113
114 7
		if ( is_array( $options[0] ) ) {
115 1
			return static::makeFromArrayOfConditions( $options );
116
		}
117
118 7
		$condition_options = static::getConditionTypeAndArguments( $options );
119 6
		$condition_class = Framework::resolve( 'framework.routing.conditions.' . $condition_options['type'] );
120
121 6
		$reflection = new ReflectionClass( $condition_class );
122 6
		$condition = $reflection->newInstanceArgs( $condition_options['arguments'] );
123 6
		return $condition;
124
	}
125
126
	/**
127
	 * Create a new condition from a closure
128
	 *
129
	 * @param  Closure            $closure
130
	 * @return ConditionInterface
131
	 */
132 1
	protected static function makeFromClosure( Closure $closure ) {
133 1
		return new CustomCondition( $closure );
134
	}
135
}
136