Completed
Push — count-subquery-clone ( d5815f...6cc328 )
by Haralan
03:48
created

Kohana_Jam_Query_Builder::set_table_name()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
c 0
b 0
f 0
rs 9.4285
ccs 7
cts 7
cp 1
cc 2
eloc 6
nc 2
nop 2
crap 2
1
<?php defined('SYSPATH') OR die('No direct script access.');
2
3
/**
4
 * Core class that all associations must extend
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 {
13
14
	/**
15
	 * Convert a column to a `table`.`column` using the appropriate model info
16
	 * @param  string $column
17
	 * @param  string $model
18
	 * @param  mixed $value
19
	 * @return string
20
	 */
21 135
	public static function resolve_attribute_name($column, $model = NULL, $value = NULL)
22
	{
23 135
		if (is_array($column))
24 135
		{
25 9
			list($column, $alias) = $column;
26 9
		}
27
28 135
		if ( ! ($column instanceof Database_Expression) AND $column !== '*')
29 135
		{
30 133
			if (strpos($column, '.') !== FALSE)
31 133
			{
32 78
				list($model, $column) = explode('.', $column);
33 78
			}
34
35 133
			if ($meta = Jam::meta(Jam_Query_Builder::aliased_model($model)))
36 133
			{
37 129
				$column = Jam_Query_Builder::resolve_meta_attribute($column, $meta, $value);
38 129
			}
39
40
			if ($model)
41 133
			{
42 132
				if (is_array($model))
43 132
				{
44 9
					$model = $model[1];
45 9
				}
46 130
				elseif ($meta)
47
				{
48 127
					$model = $meta->table();
49 127
				}
50
51 132
				$column = $model.'.'.$column;
52 132
			}
53 133
		}
54
55 135
		if ( ! empty($alias))
56 135
		{
57 9
			return array($column, $alias);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($column, $alias); (array) is incompatible with the return type documented by Kohana_Jam_Query_Builder::resolve_attribute_name of type string.

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...
58
		}
59
		else
60
		{
61 132
			return $column;
62
		}
63
	}
64
65
	/**
66
	 * Set a primary key condition. If its an array make in an IN condition.
67
	 * @param  Database_Query $query
68
	 * @param  string         $key
69
	 * @return Database_Query
70
	 */
71 24
	public static function find_by_primary_key(Database_Query $query, $key)
72
	{
73 24
		if (is_array($key))
74 24
		{
75 3
			if ( ! $key)
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
76 3
				throw new Kohana_Exception('Arrays of primary keys is empty');
77
78 3
			$query->where(':primary_key', 'IN', $key);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Database_Query as the method where() does only exist in the following sub-classes of Database_Query: Database_Query_Builder_Delete, Database_Query_Builder_Select, Database_Query_Builder_Update, Database_Query_Builder_Where, Jam_Query_Builder_Collection, Jam_Query_Builder_Delete, Jam_Query_Builder_Select, Jam_Query_Builder_Update, Kohana_Database_Query_Builder_Delete, Kohana_Database_Query_Builder_Select, Kohana_Database_Query_Builder_Update, Kohana_Database_Query_Builder_Where, Kohana_Jam_Query_Builder_Collection, Kohana_Jam_Query_Builder_Delete, Kohana_Jam_Query_Builder_Select, Kohana_Jam_Query_Builder_Update. 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...
79 3
		}
80
		else
81
		{
82 23
			if ( ! $key)
83 23
				throw new Kohana_Exception('Primary key must not be empty');
84
85 23
			$query->where(':unique_key', '=', $key);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Database_Query as the method where() does only exist in the following sub-classes of Database_Query: Database_Query_Builder_Delete, Database_Query_Builder_Select, Database_Query_Builder_Update, Database_Query_Builder_Where, Jam_Query_Builder_Collection, Jam_Query_Builder_Delete, Jam_Query_Builder_Select, Jam_Query_Builder_Update, Kohana_Database_Query_Builder_Delete, Kohana_Database_Query_Builder_Select, Kohana_Database_Query_Builder_Update, Kohana_Database_Query_Builder_Where, Kohana_Jam_Query_Builder_Collection, Kohana_Jam_Query_Builder_Delete, Kohana_Jam_Query_Builder_Select, Kohana_Jam_Query_Builder_Update. 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...
86
		}
87 24
		return $query;
88
	}
89
90
	/**
91
	 * Set the table name, even if its in an alias array (table, alias)
92
	 * @param string|array $model
93
	 * @param string $table
94
	 * @return string|array
95
	 */
96 101
	public static function set_table_name($model, $table)
97
	{
98 101
		if (is_array($model))
99 101
		{
100 9
			$model[0] = $table;
101 9
		}
102
		else
103
		{
104 92
			$model = $table;
105
		}
106 101
		return $model;
107
	}
108
109
	/**
110
	 * Convert model name to its corresponding table, even if its in an array (model, alias)
111
	 * @param  string|array $model
112
	 * @return string|array
113
	 */
114 103
	public static function resolve_table_alias($model)
115
	{
116 103
		if ($meta = Jam::meta(Jam_Query_Builder::aliased_model($model)))
117 103
		{
118 101
			$model = Jam_Query_Builder::set_table_name($model, $meta->table());
0 ignored issues
show
Bug introduced by
It seems like $meta->table() targeting Kohana_Jam_Meta::table() can also be of type object<Jam_Meta>; however, Kohana_Jam_Query_Builder::set_table_name() does only seem to accept string, 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...
119 101
		}
120 103
		return $model;
121
	}
122
123
	/**
124
	 * Generate Jam_Query_Builder_Join based on the given arguments
125
	 * @param  string  $table
126
	 * @param  string  $type                LEFT, NATURAL...
127
	 * @param  string  $context_model       the model of the parent
128
	 * @param  boolean $resolve_table_model wether to resolve the name of the model to a tablename
129
	 * @return Jam_Query_Builder_Join
130
	 */
131 35
	public static function resolve_join($table, $type = NULL, $context_model = NULL, $resolve_table_model = TRUE)
132
	{
133 35
		$context_model_name = Jam_Query_Builder::aliased_model($context_model);
134
135 35
		if ($resolve_table_model AND is_string($context_model_name) AND $meta = Jam::meta($context_model_name))
136 35
		{
137 16
			$table_name = Jam_Query_Builder::aliased_model($table);
138 16
			if (is_string($table_name) AND $association = $meta->association($table_name))
139 16
			{
140 15
				return $association->join(is_array($table) ? $table[1] : NULL, $type);
0 ignored issues
show
Bug introduced by
The method join does only exist in Jam_Association, but not in Jam_Meta.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
141
			}
142 1
		}
143
144 22
		$join = Jam_Query_Builder_Join::factory($table, $type);
145
		if ($context_model)
0 ignored issues
show
Bug Best Practice introduced by
The expression $context_model of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
146 22
		{
147 22
			$join->context_model($context_model);
148 22
		}
149 22
		return $join;
150
	}
151
152
	/**
153
	 * Return the model if its alias array (model, alias)
154
	 * @param  string|array $model
155
	 * @return string
156
	 */
157 139
	public static function aliased_model($model)
158
	{
159 139
		return is_array($model) ? $model[0] : $model;
160
	}
161
162
	/**
163
	 * Convert :primary_key, :name_kay and :unique_key to their corresponding column names
164
	 * @param  string   $attribute
165
	 * @param  Jam_Meta $meta
166
	 * @param  mixed   $value
167
	 * @return string
168
	 */
169 146
	public static function resolve_meta_attribute($attribute, Jam_Meta $meta, $value = NULL)
170
	{
171
		switch ($attribute)
172
		{
173 146
			case ':primary_key':
174 76
				$attribute = $meta->primary_key();
175 76
			break;
176
177 112
			case ':name_key':
178 10
				$attribute = $meta->name_key();
179 10
			break;
180
181 103
			case ':unique_key':
182 22
				$attribute = $meta->unique_key($value);
183 22
			break;
184
		}
185
186 146
		return $attribute;
187
	}
188
}
189