Completed
Push — master ( 39bb91...bfc7b3 )
by mw
127:18 queued 92:21
created

findCircularDescription()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 9
nc 7
nop 2
dl 0
loc 17
ccs 0
cts 0
cp 0
crap 42
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace SMW\SPARQLStore\QueryEngine\DescriptionInterpreters;
4
5
use SMW\ApplicationFactory;
6
use SMW\DIProperty;
7
use SMW\DIWikiPage;
8
use SMW\Query\Language\ConceptDescription;
9
use SMW\Query\Language\Description;
10
use SMW\Query\Language\Conjunction;
11
use SMW\Query\Language\Disjunction;
12
use SMW\SPARQLStore\QueryEngine\CompoundConditionBuilder;
13
use SMW\SPARQLStore\QueryEngine\Condition\FalseCondition;
14
use SMW\SPARQLStore\QueryEngine\DescriptionInterpreter;
15
use SMWExporter as Exporter;
16
17
/**
18
 * @license GNU GPL v2+
19
 * @since 2.1
20
 *
21
 * @author Markus Krötzsch
22
 * @author mwjames
23
 */
24
class ConceptDescriptionInterpreter implements DescriptionInterpreter {
25
26
	/**
27
	 * @var CompoundConditionBuilder
28
	 */
29
	private $compoundConditionBuilder;
30
31
	/**
32
	 * @var Exporter
33
	 */
34
	private $exporter;
35
36
	/**
37
	 * @since 2.1
38
	 *
39 9
	 * @param CompoundConditionBuilder|null $compoundConditionBuilder
40 9
	 */
41 9
	public function __construct( CompoundConditionBuilder $compoundConditionBuilder = null ) {
42 9
		$this->compoundConditionBuilder = $compoundConditionBuilder;
43
		$this->exporter = Exporter::getInstance();
44
	}
45
46
	/**
47
	 * @since 2.2
48
	 *
49 3
	 * {@inheritDoc}
50 3
	 */
51
	public function canInterpretDescription( Description $description ) {
52
		return $description instanceof ConceptDescription;
53
	}
54
55
	/**
56
	 * @since 2.2
57
	 *
58 2
	 * {@inheritDoc}
59
	 */
60 2
	public function interpretDescription( Description $description ) {
61 2
62
		$joinVariable = $this->compoundConditionBuilder->getJoinVariable();
63 2
		$orderByProperty = $this->compoundConditionBuilder->getOrderByProperty();
64 2
65
		$conceptDescription = $this->getConceptDescription(
66
			$description->getConcept()
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMW\Query\Language\Description as the method getConcept() does only exist in the following sub-classes of SMW\Query\Language\Description: SMW\Query\Language\ConceptDescription. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
67 2
		);
68 1
69
		if ( $conceptDescription === '' ) {
70
			return new FalseCondition();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \SMW\SPARQLSt...ition\FalseCondition(); (SMW\SPARQLStore\QueryEng...ondition\FalseCondition) is incompatible with the return type declared by the interface SMW\SPARQLStore\QueryEng...r::interpretDescription of type SMW\SPARQLStore\QueryEngine\Condition.

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...
71 1
		}
72
73 1
		$hash = 'concept-' . $conceptDescription->getQueryString();
74
75 1
		$this->compoundConditionBuilder->getCircularReferenceGuard()->mark( $hash );
76
77
		if ( $this->compoundConditionBuilder->getCircularReferenceGuard()->isCircularByRecursionFor( $hash ) ) {
78
79
			$this->compoundConditionBuilder->addError(
80
				array( 'smw-query-condition-circular', $description->getQueryString() )
0 ignored issues
show
Documentation introduced by
array('smw-query-conditi...tion->getQueryString()) is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
81
			);
82
83
			return new FalseCondition();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \SMW\SPARQLSt...ition\FalseCondition(); (SMW\SPARQLStore\QueryEng...ondition\FalseCondition) is incompatible with the return type declared by the interface SMW\SPARQLStore\QueryEng...r::interpretDescription of type SMW\SPARQLStore\QueryEngine\Condition.

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...
84 1
		}
85 1
86
		$this->compoundConditionBuilder->setJoinVariable( $joinVariable );
87 1
		$this->compoundConditionBuilder->setOrderByProperty( $orderByProperty );
88
89
		$condition = $this->compoundConditionBuilder->mapDescriptionToCondition(
90
			$conceptDescription
0 ignored issues
show
Bug introduced by
It seems like $conceptDescription defined by $this->getConceptDescrip...cription->getConcept()) on line 65 can also be of type string; however, SMW\SPARQLStore\QueryEng...escriptionToCondition() does only seem to accept object<SMW\Query\Language\Description>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
91 1
		);
92
93 1
		$this->compoundConditionBuilder->getCircularReferenceGuard()->unmark( $hash );
94
95
		return $condition;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $condition; (SMW\SPARQLStore\QueryEngine\Condition\Condition) is incompatible with the return type declared by the interface SMW\SPARQLStore\QueryEng...r::interpretDescription of type SMW\SPARQLStore\QueryEngine\Condition.

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...
96 2
	}
97
98 2
	private function getConceptDescription( DIWikiPage $concept ) {
99
100 2
		$applicationFactory = ApplicationFactory::getInstance();
101 2
102
		$value = $applicationFactory->getStore()->getSemanticData( $concept )->getPropertyValues(
103
			new DIProperty( '_CONC' )
104 2
		);
105 1
106
		if ( $value === null || $value === array() ) {
107
			return '';
108 1
		}
109
110 1
		$value = end( $value );
111 1
112
		$description = $applicationFactory->newQueryParser()->getQueryDescription(
0 ignored issues
show
Deprecated Code introduced by
The method SMW\ApplicationFactory::newQueryParser() has been deprecated with message: since 2.5, use QueryFactory::newQueryParser

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
113
			$value->getConceptQuery()
114
		);
115
116
		$this->findCircularDescription( $concept, $description );
117
118
		return $description;
119
	}
120
121
	private function findCircularDescription( $concept, $description ) {
122
123
		if ( $description instanceof ConceptDescription ) {
124
			if ( $description->getConcept()->equals( $concept ) ) {
125
				$this->compoundConditionBuilder->addError(
126
					array( 'smw-query-condition-circular', $description->getQueryString() )
0 ignored issues
show
Documentation introduced by
array('smw-query-conditi...tion->getQueryString()) is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
				);
128
				return;
129
			}
130
		}
131
132
		if ( $description instanceof Conjunction || $description instanceof Disjunction ) {
133
			foreach ( $description->getDescriptions() as $desc ) {
134
				$this->findCircularDescription( $concept, $desc );
135
			}
136
		}
137
	}
138
139
}
140