Completed
Push — master ( e73d65...d818c3 )
by J.D.
03:52
created

WordPoints_WPDB_Wrapper::__call()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 23
c 1
b 0
f 0
nc 10
nop 2
dl 0
loc 38
rs 8.439
1
<?php
2
3
/**
4
 * .
5
 *
6
 * @package wordpoints-hooks-api
7
 * @since   1.
8
 */
9
10
class WordPoints_WPDB_Wrapper {
11
12
	protected $wpdb;
13
14
	public function __construct( $wpdb ) {
15
		$this->wpdb = $wpdb;
16
	}
17
18
	public function __get( $name ) {
19
		return $this->wpdb->$name;
20
	}
21
22
	public function __set( $name, $value ) {
23
		$this->wpdb->$name = $value;
24
	}
25
26
	public function __isset( $name ) {
27
		return isset( $this->wpdb->$name );
28
	}
29
30
	public function __unset( $name ) {
31
		unset( $this->wpdb->$name );
32
	}
33
34
	public function __call( $name, $arguments ) {
35
36
		$wrappers = wordpoints_apps()->sub_apps->get( 'wpdb_wrappers' );
37
		$slugs = $wrappers->get_child_slugs( $name );
0 ignored issues
show
Unused Code introduced by
$slugs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
38
		$should_listen = $query = false;
0 ignored issues
show
Unused Code introduced by
$should_listen is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
39
40
41
		switch ( $name ) {
42
43
			case 'insert':
44
//			case 'replace':
45
				$query = new WordPoints_WPDB_Query_Data_Array( $this->args[0] );
0 ignored issues
show
Documentation introduced by
The property args does not exist on object<WordPoints_WPDB_Wrapper>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
46
			break;
47
48
			case 'update':
49
				$query =  new WordPoints_WPDB_Query_Data_Array( $this->args[0], $this->args[2] );
0 ignored issues
show
Documentation introduced by
The property args does not exist on object<WordPoints_WPDB_Wrapper>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
50
			break;
51
52
			case 'delete':
53
				$query =  new WordPoints_WPDB_Query_Data_Array( $this->args[0], $this->args[1] );
0 ignored issues
show
Documentation introduced by
The property args does not exist on object<WordPoints_WPDB_Wrapper>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
54
			break;
55
56
			case 'query':
57
				$query =  new WordPoints_WPDB_Query_Data_SQL( $this->args[0] );
0 ignored issues
show
Documentation introduced by
The property args does not exist on object<WordPoints_WPDB_Wrapper>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
58
			break;
59
		}
60
61
		if ( $query ) {
62
			/** @var WordPoints_WPDB_Query_WrapperI $wrapper */
63
			$wrapper = $wrappers->get( $name, $query );
64
65
			$result = $wrapper->execute();
66
		} else {
67
			$result = call_user_func_array( array( $this->wpdb, $name ), $arguments );
68
		}
69
70
		return $result;
71
	}
72
}
73
74
75
interface WordPoints_WPDB_Query_Data {
76
	public function get_table_name();
77
	public function get_where_clause();
78
}
79
80
interface WordPoints_MySQL_Query_Parser_SimpleI {
81
	public function get_table_name();
82
	public function get_where_clause();
83
}
84
85
class WordPoints_MySQL_Query_Parser_Simple_Insert implements WordPoints_MySQL_Query_Parser_SimpleI {
86
87
	public function __construct( $sql ) {
88
		$this->sql = $sql;
0 ignored issues
show
Bug introduced by
The property sql does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
89
	}
90
91
	public function get_table_name() {
92
		// TODO: Implement get_table_name() method.
93
	}
94
95
	public function get_where_clause() {
96
97
		// we'd have to get the keys from the database table, to know what the where
98
		// was aexactly. but we can just get the complete where, even though some
99
		// columns will not be needed. then the other code can decide what to do.
100
		// although I guess sense we have the tablel name we could reun the query
101
		// ourselves if we need/want to.
102
		if ( stripos( $this->sql, 'ON DUPLICATE KEY UPDATE' ) ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
103
104
		}
105
106
		return '';
107
	}
108
}
109
110
class WordPoints_WPDB_Query_Data_SQL implements WordPoints_WPDB_Query_Data {
111
112
	protected $sql;
113
	/**
114
	 *
115
	 *
116
	 * @since 1.
117
	 *
118
	 * @var WordPoints_MySQL_Query_Parser_SimpleI
119
	 */
120
	protected $parser;
121
122
	public function __construct( $sql ) {
123
		$this->sql = $sql;
124
		$this->parser = $this->get_parser();
125
	}
126
127
	protected function get_parser() {
128
		// may need to determine parser based on query type, however, it may be that
129
		// we actually need to parse the query somewhat just to determine what kind
130
		// of query it is.
131
		// maybe the parser will actually be passed in, and the query type deterimed
132
		// by teh method wrapper.
133
		$parser = new WordPoints_MySQL_Query_Parser_Simple_Insert( $this->sql );
134
		// maybe we don't even need to parse.
135
		return $parser;
136
	}
137
138
	public function get_table_name() {
139
		return $this->parser->get_table_name();
140
	}
141
142
	public function get_where_clause() {
143
		$this->parser->get_where_clause();
144
	}
145
}
146
147
class WordPoints_WPDB_Query_Data_Array implements WordPoints_WPDB_Query_Data {
148
149
	protected $table_name;
150
	protected $where_data;
151
152
	public function __construct( $table_name, $where_data = array() ) {
153
		$this->table_name = $table_name;
154
		$this->where_data = $where_data;
155
	}
156
157
	public function get_table_name() {
158
		return $this->table_name;
159
	}
160
161
	public function get_where_clause() {
162
		// build the WHERE clause from the data.
163
	}
164
}
165
166
interface WordPoints_WPDB_Query_WrapperI {
167
	public function __construct( $slug, $args, $wpdb );
168
	public function execute();
169
170
}
171
172
abstract class WordPoints_WPDB_Query_Wrapper implements WordPoints_WPDB_Query_WrapperI {
173
174
	protected $slug;
175
	protected $args;
176
	protected $wpdb;
177
	/**
178
	 *
179
	 *
180
	 * @since 1.
181
	 *
182
	 * @var WordPoints_WPDB_Query_Data|false
183
	 */
184
	protected $data;
185
	protected $result;
186
	protected $backed_up_wpdb_vars;
187
188
	public function __construct( $slug, $args, $wpdb ) {
189
		$this->slug = $slug;
190
		$this->args = $args;
191
		$this->wpdb = $wpdb;
192
		$this->data = $this->get_query_data();
193
	}
194
195
	protected function get_query_data() {
196
197
		switch ( $this->slug ) {
198
199
			case 'insert':
200
				//			case 'replace':
201
				return new WordPoints_WPDB_Query_Data_Array( $this->args[0] );
202
203
			case 'update':
204
				return new WordPoints_WPDB_Query_Data_Array( $this->args[0], $this->args[2] );
205
206
			case 'delete':
207
				return new WordPoints_WPDB_Query_Data_Array( $this->args[0], $this->args[1] );
208
209
			case 'query':
210
				return new WordPoints_WPDB_Query_Data_SQL( $this->args[0] );
211
		}
212
213
		return false;
214
	}
215
216
	public function execute() {
217
218
		$should_listen = $this->should_listen();
219
220
		if ( $should_listen ) {
221
			$this->before_query();
222
		}
223
224
		$this->result = call_user_func_array( array( $this->wpdb, $this->slug ), $this->args );
225
226
		if ( $should_listen && $this->was_successful_query() ) {
227
			$this->backup_wpdb_vars();
228
			$this->after_query();
229
			$this->restore_wpdb_vars();
230
		}
231
232
		return $this->result;
233
	}
234
235
	protected function backup_wpdb_vars() {
236
		$to_backup = array(
237
			// public
238
			'last_error',
239
			'num_rows',
240
			'insert_id',
241
			'func_call',
242
			
243
			// marked private but still public
244
			'rows_affected',
245
			'last_query',
246
			'last_result',
247
			
248
			// protected but magic getter
249
			'col_info',
250
			'result',
251
		);
252
253
		// we could check for the availableility of eahc valu here in case of dropins
254
		// or they become not available in the future.
255
		foreach ( $to_backup as $property ) {
256
			$this->backed_up_wpdb_vars[ $property ] = $this->wpdb->$property;
257
		}
258
	}
259
260
	protected function restore_wpdb_vars() {
261
262
		// make sure that the last result is freed, since we're going to overwrite it
263
		// with the backed up result.
264
		$this->wpdb->flush();
265
266
		foreach ( $this->backed_up_wpdb_vars as $property => $value ) {
267
			$this->wpdb->$property = $value;
268
		}
269
	}
270
271
	abstract public function should_listen();
272
273
	abstract protected function before_query();
274
275
	abstract protected function was_successful_query();
276
277
	abstract protected function after_query();
278
}
279
280
abstract class WordPoints_WPDB_Query_Wrapper_Basic extends WordPoints_WPDB_Query_Wrapper {
281
282
	/**
283
	 *
284
	 *
285
	 * @since 1.
286
	 *
287
	 * @var WordPoints_Entity
288
	 */
289
	protected $entity;
290
	protected $table_name;
291
292
	/**
293
	 * check if this is a table that we want to listen for.
294
	 * see if the table name matches an entity name.
295
	 * if not, try removing the base prefix, then the site prefix, etc. until
296
	 * we find a similar entity. then we can doulbe-check that we have the right
297
	 * entity by then getting the table name form the entity object and checking
298
	 * that it matches. If all else fails we can loop through the entities, or
299
	 * else maybe have a cache of the table name index that we can use as a
300
	 * look-up
301
	 */
302
	protected function is_entity_table() {
303
304
		$this->entity = 'new entity object' . $this->data->get_table_name();
0 ignored issues
show
Documentation Bug introduced by
It seems like 'new entity object' . $t...>data->get_table_name() of type string is incompatible with the declared type object<WordPoints_Entity> of property $entity.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
305
306
		return true;
307
	}
308
309
	public function should_listen() {
310
311
		if ( ! $this->data ) {
312
			return false;
313
		}
314
315
		if ( $this->is_entity_table() ) {
316
			return false;
317
		}
318
319
		return true;
320
	}
321
322
	protected function was_successful_query() {
323
		return (bool) wordpoints_posint( $not_by_ref = $this->wpdb->num_rows );
324
	}
325
}
326
327
class WordPoints_WPDB_Query_Wrapper_Query implements WordPoints_WPDB_Query_WrapperI {
328
329
	protected $slug;
330
	protected $args;
331
	protected $wpdb;
332
333
	public function __construct( $slug, $args, $wpdb ) {
334
		$this->slug = $slug;
335
		$this->args = $args;
336
		$this->wpdb = $wpdb;
337
	}
338
339
	public function execute() {
340
341
		// check if this is a query that we want to listen to.
342
		// if so, call execute on a particular child.
343
		// otherwise, just call execute.
344
		if ( $this->get_query_type() ) {
0 ignored issues
show
Bug introduced by
The method get_query_type() does not seem to exist on object<WordPoints_WPDB_Query_Wrapper_Query>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
345
346
		} else {
347
			$result = call_user_func_array( array( $this->wpdb, $this->slug ), $this->args );
348
		}
349
350
		return $result;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
351
	}
352
}
353
354
class WordPoints_WPDB_Query_Wrapper_Update extends WordPoints_WPDB_Query_Wrapper_Basic {
355
//	 get a "before" snapshot of the entities.
356
//	 run a query based on the $where args.
357
//	 acutally, not needed. just grab the where args. that tells us what was
358
//	 being changed. the only thing that we'd need was the ids of each of the
359
//	 entities taht actually was changed. however, I guess maybe we can't know
360
//	 which entities actually changed, and how much they changed (which atts),
361
//	 unless we actually query pull out the "before" snapshots for each one.
362
//	 I suppose in some simple cases when the ID is supplied in where, there
363
//	 is only one entity to be updated. but we'd still need the before snapshot
364
//	 unless there was only one attribute being updated, in which case it would
365
//	 be safe to assumte that hte attribute was updated wihtou needing a before
366
//	 snapshot, just by checkin gthe nubmer of affected rows. However,  that
367
//	 might not be worht it.
368
	protected function before_query() {
369
370
		$ids = $this->get_entity_ids();
371
372
		$this->entities = new WordPoints_Entity_Array( $this->entity->get_slug() );
0 ignored issues
show
Bug introduced by
The property entities does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
373
		$this->entities->set_the_value( $ids );
374
	}
375
376
	protected function get_entity_ids() {
377
		$where = $this->data->get_where_clause();
378
		$id_field = wordpoints_escape_mysql_identifier( $this->entity->get_id_field() );
379
		$table_name = wordpoints_escape_mysql_identifier( $this->data->get_table_name() );
380
		return $this->wpdb->get_col( "SELECT {$id_field} FROM {$table_name} {$where}" );
381
	}
382
383
	protected function after_query() {
384
385
		// I guess we invoke the api to run the action now?
386
		$this->trigger_entity_update_actions();
0 ignored issues
show
Bug introduced by
The method trigger_entity_update_actions() does not seem to exist on object<WordPoints_WPDB_Query_Wrapper_Update>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
387
	}
388
}
389
390
class WordPoints_WPDB_Query_Wrapper_Insert extends WordPoints_WPDB_Query_Wrapper_Basic {
391
392
	
393
	protected function before_query() {
394
395
		// in case of on duplicate key update.
396
		$where_clause = $this->data->get_where_clause();
397
398
		// get a before snapshot, if any matching are found.
399
		if ( $where_clause ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
400
401
		}
402
	}
403
404
	protected function after_query() {
405
		$this->entity->set_the_value( $this->wpdb->insert_id );
406
		// I guess we invoke the api to run the action now?
407
		$this->trigger_entity_create_actions();
0 ignored issues
show
Bug introduced by
The method trigger_entity_create_actions() does not seem to exist on object<WordPoints_WPDB_Query_Wrapper_Insert>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
408
	}
409
}
410
411
412
class WordPoints_WPDB_Query_Wrapper_Delete extends WordPoints_WPDB_Query_Wrapper_Update {
413
414
	protected function after_query() {
415
		// I guess we invoke the api to run the action now?
416
		$this->trigger_entity_delete_actions( $this->entities );
0 ignored issues
show
Bug introduced by
The property entities does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
The method trigger_entity_delete_actions() does not seem to exist on object<WordPoints_WPDB_Query_Wrapper_Delete>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
417
	}
418
}
419
420
421
422
// EOF
423