Kohana_Jam_Query_Builder_Select::_join()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 9.7
c 0
b 0
f 0
cc 3
nc 4
nop 3
crap 3
1
<?php defined('SYSPATH') OR die('No direct script access.');
2
3
/**
4
 * A class to create queries for selecting data for jam models from the database
5
 *
6
 * @package    Jam
7
 * @category   Associations
8
 * @author     Ivan Kerin
9
 * @copyright  (c) 2011-2012 Despark Ltd.
10
 * @license    http://www.opensource.org/licenses/isc-license.txt
11
 */
12
abstract class Kohana_Jam_Query_Builder_Select extends Database_Query_Builder_Select {
13
14
	/**
15
	 * Create object of class Jam_Query_Builder_Select
16
	 * @param  string $model
17
	 * @return Jam_Query_Builder_Select
18
	 */
19 1
	public static function factory($model)
20
	{
21 1
		return new Jam_Query_Builder_Select($model);
22
	}
23
24
	/**
25
	 * @var  Jam_Meta  The meta object (if found) that is attached to this builder
26
	 */
27
	protected $_meta = NULL;
28
29
	/**
30
	 * A store for user defined values for the builder
31
	 * @var array
32
	 */
33
	protected $_params = array();
34
35
	protected static $_modifiable = array('select', 'from', 'join', 'where', 'group_by', 'having', 'order_by', 'union', 'distinct', 'limit', 'offset', 'parameters');
36
37
	/**
38
	 * Constructs a new Jam_Builder instance.
39
	 *
40
	 * $model is not actually allowed to be NULL. It has
41
	 * a default because PHP throws strict errors otherwise.
42
	 *
43
	 * @throws  Kohana_Exception
44
	 * @param   string|null  $model
45
	 * @param   mixed|null   $key
0 ignored issues
show
Bug introduced by
There is no parameter named $key. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
46
	 */
47 140
	public function __construct($model)
48
	{
49 140
		parent::__construct();
50
51 140
		$this->_meta  = Jam::meta($model);
52
53 140
		if ( ! $this->_meta)
54
			throw new Kohana_Exception('There is no model :model for select', array(':model' => $model));
55
56 140
		$this->meta()->events()->trigger('builder.after_construct', $this);
57 140
	}
58
59 22
	public function where_key($unique_key)
60
	{
61 22
		Jam_Query_Builder::find_by_primary_key($this, $unique_key);
62
63 22
		return $this;
64
	}
65
66 91
	public function compile($db = NULL)
67
	{
68 91
		if ($db === NULL AND $this->meta())
69
		{
70 43
			$db = Database::instance($this->meta()->db());
0 ignored issues
show
Bug introduced by
It seems like $this->meta()->db() targeting Kohana_Jam_Meta::db() can also be of type object<Jam_Meta>; however, Kohana_Database::instance() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
71
		}
72
73 91
		$original_select = $this->_select;
74 91
		$original_from = $this->_from;
75
76 91
		if (empty($this->_from))
77
		{
78 89
			$this->_from[] = $this->meta()->model();
79
		}
80
81 91
		if (empty($this->_select))
82
		{
83 78
			$this->_select[] = $this->meta()->table().'.*';
84
		}
85
86 91
		foreach ($this->_from as & $from)
87
		{
88 91
			$from = Jam_Query_Builder::resolve_table_alias($from);
89
		}
90
91 91
		foreach ($this->_select as & $attribute)
92
		{
93 91
			$attribute = Jam_Query_Builder::resolve_attribute_name($attribute, $this->meta()->model());
94
		}
95
96 91
		$this->meta()->events()->trigger('builder.before_select', $this);
97
98 91
		$result = parent::compile($db);
99
100 91
		$this->meta()->events()->trigger('builder.after_select', $this);
101
102 91
		$this->_select = $original_select;
103 91
		$this->_from = $original_from;
104
105 91
		return $result;
106
	}
107
108 52
	public function execute($db = NULL, $as_object = NULL, $object_params = NULL)
109
	{
110 52
		if ($db === NULL AND $this->meta())
111
		{
112 52
			$db = Database::instance($this->meta()->db());
0 ignored issues
show
Bug introduced by
It seems like $this->meta()->db() targeting Kohana_Jam_Meta::db() can also be of type object<Jam_Meta>; however, Kohana_Database::instance() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
113
		}
114
115 52
		return parent::execute($db, $as_object, $object_params);
116
	}
117
118 19
	protected function _join($association, $type, $resolve_table_model = TRUE)
119
	{
120 19
		$join_key = is_array($association) ? join(':', $association) : $association;
121
122 19
		if ( ! isset($this->_join[$join_key]))
123
		{
124 19
			$join = Jam_Query_Builder::resolve_join($association, $type, $this->meta()->model(), $resolve_table_model);
125
126 19
			$this->_join[$join_key] = $join;
127
128 19
			return $join;
129
		}
130
		else
131
		{
132 1
			return $this->_join[$join_key];
133
		}
134
	}
135
136 8
	public function join($association, $type = NULL)
137
	{
138 8
		$this->_last_join = $this->_join($association, $type);
139
140 8
		return $this;
141
	}
142
143 1
	public function on($c1, $op, $c2)
144
	{
145 1
		if ( ! $this->_last_join)
146
			throw new Kohana_Exception('You must specifiy a JOIN first!');
147
148 1
		return parent::on($c1, $op, $c2);
149
	}
150
151
152 1
	public function join_nested($association, $type = NULL)
153
	{
154 1
		return $this->_join($association, $type)->end($this);
155
	}
156
157 10
	public function join_table($table, $type = NULL)
158
	{
159 10
		return $this->_join($table, $type, FALSE)->end($this);
160
	}
161
162 32
	protected function _compile_order_by(Database $db, array $order_by)
163
	{
164 32
		foreach ($order_by as & $order)
165
		{
166 32
			$order[0] = Jam_Query_Builder::resolve_attribute_name($order[0], $this->meta()->model());
167
		}
168
169 32
		return parent::_compile_order_by($db, $order_by);
170
	}
171
172 1
	protected function _compile_group_by(Database $db, array $group_by)
173
	{
174 1
		foreach ($group_by as & $group)
175
		{
176 1
			$group = Jam_Query_Builder::resolve_attribute_name($group, $this->meta()->model());
177
		}
178
179 1
		return parent::_compile_group_by($db, $group_by);
180
	}
181
182 74
	protected function _compile_conditions(Database $db, array $conditions)
183
	{
184 74
		foreach ($conditions as & $group)
185
		{
186 74
			foreach ($group as & $condition)
187
			{
188 74
				if (is_array($condition))
189
				{
190 74
					$condition[0] = Jam_Query_Builder::resolve_attribute_name($condition[0], $this->meta()->model(), $condition[2]);
191
				}
192
			}
193
		}
194
195 74
		return parent::_compile_conditions($db, $conditions);
196
	}
197
198 16
	public function aggregate_query($function, $column = NULL)
199
	{
200 16
		if ($column === NULL OR $column === '*')
201
		{
202 12
			$column = '*';
203
		}
204
		else
205
		{
206 4
			$db = Database::instance($this->meta()->db());
0 ignored issues
show
Bug introduced by
It seems like $this->meta()->db() targeting Kohana_Jam_Meta::db() can also be of type object<Jam_Meta>; however, Kohana_Database::instance() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
207 4
			$column = Jam_Query_Builder::resolve_attribute_name($column, $this->meta()->model());
208 4
			$column = $db->quote_column($column);
209
		}
210
211 16
		$count = clone $this;
212 16
		return $count->select(array(DB::expr("{$function}({$column})"), 'result'));
213
	}
214
215
	public function aggregate($function, $column = NULL)
216
	{
217
		return $this->aggregate_query($function, $column)->execute()->get('result');
218
	}
219
220 10
	public function count_all($without_grouping = FALSE)
221
	{
222 10
		$query = $this->aggregate_query('COUNT');
223
224 10
		$query->except('order_by');
225
226 10
		if ($without_grouping)
227
		{
228
			$query->except('group_by', 'limit', 'offset');
229
		}
230
231 10
		return (int) $query->execute()->get('result');
232
	}
233
234
	public function count_with_subquery()
235
	{
236
		$query = clone $this;
237
		$query->except('order_by');
238
		return (int) DB::select(array(DB::expr('COUNT(*)'), 'result'))->from(array($query, 'result_table'))->execute($query->meta()->db())->get('result');
239
	}
240
241
	/**
242
	 * Passes unknown methods along to the behaviors.
243
	 *
244
	 * @param   string  $method
245
	 * @param   array   $args
246
	 * @return  mixed
247
	 **/
248 40
	public function __call($method, $args)
249
	{
250 40
		$return = $this->_meta->events()->trigger_callback('builder', $this, $method, $args);
251 32
		return ($return !== NULL) ? $return : $this;
252
	}
253
254
	/**
255
	 * Getter/setter for the params array used to store arbitrary values by the behaviors
256
	 *
257
	 * @param  array|string $params
258
	 * @param  mixed $param
259
	 * @return Jam_Builder         $this
260
	 */
261 29
	public function params($params = NULL, $param = NULL)
262
	{
263
		// Accept params('name', 'param');
264 29
		if ($param !== NULL)
265
		{
266 2
			$params = array($params => $param);
267
		}
268
269 29
		if (is_array($params))
270
		{
271 2
			$this->_params = Arr::merge($params, $this->_params);
272 2
			return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Kohana_Jam_Query_Builder_Select) is incompatible with the return type documented by Kohana_Jam_Query_Builder_Select::params of type Jam_Builder.

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...
273
		}
274
275 29
		if (is_string($params))
276
		{
277 29
			return Arr::get($this->_params, $params);
278
		}
279
280
		return $this->_params;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_params; (array) is incompatible with the return type documented by Kohana_Jam_Query_Builder_Select::params of type Jam_Builder.

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...
281
	}
282
283 140
	public function meta()
284
	{
285 140
		return $this->_meta;
286
	}
287
288
	/**
289
	 * You can get some of the parameters of the jam query builder.
290
	 *
291
	 * @param  string $name one of select, from, join, where, group_by, having, order_by, union, distinct, limit, offset, parameters
292
	 * @return mixed
293
	 */
294 30
	public function __get($name)
295
	{
296 30
		if ( ! in_array($name, Jam_Query_Builder_Select::$_modifiable))
0 ignored issues
show
Documentation introduced by
The property $_modifiable is declared protected in Kohana_Jam_Query_Builder_Select. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
297
				throw new Kohana_Exception('You cannot get :name, only :modifiable', array(':name' => $name, ':modifiable' => join(', ', Jam_Query_Builder_Select::$_modifiable)));
0 ignored issues
show
Documentation introduced by
The property $_modifiable is declared protected in Kohana_Jam_Query_Builder_Select. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
298
299 30
		return $this->{'_'.$name};
300
	}
301
302 43
	public function __toString()
303
	{
304
		try
305
		{
306
			// Return the SQL string
307 43
			return $this->compile();
308
		}
309
		catch (Exception $e)
310
		{
311
			return Kohana_Exception::text($e);
312
		}
313
	}
314
315 14
	public function except($name)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
316
	{
317 14
		$except = func_get_args();
318
319 14
		if ($not_modifiable = array_diff($except, Jam_Query_Builder_Select::$_modifiable))
0 ignored issues
show
Documentation introduced by
The property $_modifiable is declared protected in Kohana_Jam_Query_Builder_Select. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
320 1
				throw new Kohana_Exception('You cannot modify :not_modifiable, only :modifiable', array(':not_modifiable' => join(', ', $not_modifiable), ':modifiable' => join(', ', Jam_Query_Builder_Select::$_modifiable)));
0 ignored issues
show
Documentation introduced by
The property $_modifiable is declared protected in Kohana_Jam_Query_Builder_Select. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
321
322 13
		$new = new Database_Query_Builder_Select;
323
324 13
		foreach ($except as $name)
325
		{
326 13
			$this->{'_'.$name} = $new->{'_'.$name};
327
		}
328
329 13
		return $this;
330
	}
331
}
332