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\SQLStore\QueryEngine\DescriptionInterpreters;
4
5
use SMW\Query\Language\ConceptDescription;
6
use SMW\Query\Language\Description;
7
use SMW\Query\Language\Conjunction;
8
use SMW\Query\Language\Disjunction;
9
use SMW\SQLStore\QueryEngine\DescriptionInterpreter;
10
use SMW\SQLStore\QueryEngine\QuerySegment;
11
use SMW\SQLStore\QueryEngine\QuerySegmentListBuilder;
12
use SMWQueryParser as QueryParser;
13
use SMWSQLStore3;
14
15
/**
16
 * @license GNU GPL v2+
17
 * @since 2.2
18
 *
19
 * @author Markus Krötzsch
20
 * @author Jeroen De Dauw
21
 * @author mwjames
22
 */
23
class ConceptDescriptionInterpreter implements DescriptionInterpreter {
24
25
	/**
26
	 * @var QuerySegmentListBuilder
27
	 */
28
	private $querySegmentListBuilder;
29
30
	/**
31
	 * @since 2.2
32
	 *
33 184
	 * @param QuerySegmentListBuilder $querySegmentListBuilder
34 184
	 */
35 184
	public function __construct( QuerySegmentListBuilder $querySegmentListBuilder ) {
36
		$this->querySegmentListBuilder = $querySegmentListBuilder;
37
	}
38
39
	/**
40
	 * @since 2.2
41
	 *
42 30
	 * @return boolean
43 30
	 */
44
	public function canInterpretDescription( Description $description ) {
45
		return $description instanceof ConceptDescription;
46
	}
47
48
	/**
49
	 * @since 2.2
50
	 *
51
	 * @param Description $description
52
	 *
53 8
	 * @return QuerySegment
54
	 */
55 8
	public function interpretDescription( Description $description ) {
56
57 8
		$query = new QuerySegment();
58 8
		$concept = $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...
59 8
60 8
		$conceptId = $this->querySegmentListBuilder->getStore()->getObjectIds()->getSMWPageID(
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMW\Store as the method getObjectIds() does only exist in the following sub-classes of SMW\Store: SMWSQLStore3, SMWSparqlStore, SMW\SPARQLStore\SPARQLStore. 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...
61 8
			$concept->getDBkey(),
62
			SMW_NS_CONCEPT,
63
			'',
64 8
			''
65
		);
66 8
67
		$hash = 'concept-' . $conceptId;
68 8
69
		$this->querySegmentListBuilder->getCircularReferenceGuard()->mark( $hash );
70 2
71 2
		if ( $this->querySegmentListBuilder->getCircularReferenceGuard()->isCircularByRecursionFor( $hash ) ) {
72
73
			$this->querySegmentListBuilder->addError(
74 2
				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...
75
			);
76
77 7
			return $query;
78 7
		}
79
80
		$db = $this->querySegmentListBuilder->getStore()->getConnection( 'mw.db.queryengine' );
81 7
		$row = $this->getConceptForId( $db, $conceptId );
82 1
83
		// No description found, concept does not exist.
84
		if ( $row === false ) {
85
			$this->querySegmentListBuilder->getCircularReferenceGuard()->unmark( 'concept-' . $conceptId );
86 1
			// keep the above query object, it yields an empty result
87
			// TODO: announce an error here? (maybe not, since the query processor can check for
88
			// non-existing concept pages which is probably the main reason for finding nothing here)
89 6
			return $query;
90
		};
91 6
92 6
		global $smwgQConceptCaching, $smwgQMaxSize, $smwgQMaxDepth, $smwgQFeatures, $smwgQConceptCacheLifetime;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
93 6
94
		$may_be_computed = ( $smwgQConceptCaching == CONCEPT_CACHE_NONE ) ||
95 6
		    ( ( $smwgQConceptCaching == CONCEPT_CACHE_HARD ) && ( ( ~( ~( $row->concept_features + 0 ) | $smwgQFeatures ) ) == 0 ) &&
96 2
		      ( $smwgQMaxSize >= $row->concept_size ) && ( $smwgQMaxDepth >= $row->concept_depth ) );
97 6
98
		if ( $row->cache_date &&
99 2
		     ( ( $row->cache_date > ( strtotime( "now" ) - $smwgQConceptCacheLifetime * 60 ) ) ||
100 2
		       !$may_be_computed ) ) { // Cached concept, use cache unless it is dead and can be revived.
101 2
102 5
			$query->joinTable = SMWSQLStore3::CONCEPT_CACHE_TABLE;
103 5
			$query->joinfield = "$query->alias.s_id";
104 4
			$query->where = "$query->alias.o_id=" . $db->addQuotes( $conceptId );
105
		} elseif ( $row->concept_txt ) { // Parse description and process it recursively.
106 4
			if ( $may_be_computed ) {
107 4
				$description = $this->getConceptQueryDescriptionFrom( $row->concept_txt );
108
109 4
				$this->findCircularDescription(
110
					$concept,
111
					$description
112 1
				);
113 1
114
				$qid = $this->querySegmentListBuilder->getQuerySegmentFrom( $description );
115
116
				if ($qid != -1) {
117
					$query = $this->querySegmentListBuilder->findQuerySegment( $qid );
118 6
				} else { // somehow the concept query is no longer valid; maybe some syntax changed (upgrade) or global settings were modified since storing it
119
					$this->querySegmentListBuilder->addError( 'smw_emptysubquery' ); // not the right message, but this case is very rare; let us not make detailed messages for this
120 6
				}
121
			} else {
122
				$this->querySegmentListBuilder->addError(
123
					array( 'smw_concept_cache_miss', $concept->getDBkey() )
0 ignored issues
show
Documentation introduced by
array('smw_concept_cache..., $concept->getDBkey()) is of type array<integer,?,{"0":"string","1":"?"}>, 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...
124
				);
125
			}
126
		} // else: no cache, no description (this may happen); treat like empty concept
127
128
		$this->querySegmentListBuilder->getCircularReferenceGuard()->unmark( $hash );
129
130 7
		return $query;
131 7
	}
132 7
133 7
	/**
134 7
	 * We bypass the storage interface here (which is legal as we control it,
135 7
	 * and safe if we are careful with changes ...)
136
	 *
137
	 * This should be faster, but we must implement the unescaping that concepts
138
	 * do on getWikiValue
139
	 */
140
	private function getConceptForId( $db, $id ) {
141
		return $db->selectRow(
142
			'smw_fpt_conc',
143 4
			array( 'concept_txt', 'concept_features', 'concept_size', 'concept_depth', 'cache_date' ),
144 4
			array( 's_id' => $id ),
145
			__METHOD__
146 4
		);
147 4
	}
148
149
	/**
150
	 * No defaultnamespaces here; If any, these are already in the concept.
151
	 * Unescaping is the same as in SMW_DV_Conept's getWikiValue().
152
	 */
153
	private function getConceptQueryDescriptionFrom( $conceptQuery ) {
154
		$queryParser = new QueryParser();
155
156
		return $queryParser->getQueryDescription(
157
			str_replace( array( '&lt;', '&gt;', '&amp;' ), array( '<', '>', '&' ), $conceptQuery )
158
		);
159
	}
160
161
	private function findCircularDescription( $concept, &$description ) {
162
163
		if ( $description instanceof ConceptDescription ) {
164
			if ( $description->getConcept()->equals( $concept ) ) {
165
				$this->querySegmentListBuilder->addError(
166
					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...
167
				);
168
				return;
169
			}
170
		}
171
172
		if ( $description instanceof Conjunction || $description instanceof Disjunction ) {
173
			foreach ( $description->getDescriptions() as $desc ) {
174
				$this->findCircularDescription( $concept, $desc );
175
			}
176
		}
177
	}
178
179
}
180