Completed
Push — association-fixes ( e74f25 )
by Danail
08:02
created

Kohana_Jam_Association_Hasmany   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 78%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 47
c 4
b 2
f 0
lcom 1
cbo 9
dl 0
loc 334
ccs 117
cts 150
cp 0.78
rs 8.439

15 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 19 4
A join() 0 13 3
A collection() 0 13 2
B set() 0 15 6
C model_before_delete() 0 24 7
A clear() 0 9 2
A erase_query() 0 17 3
A nullify_query() 0 19 3
C remove_items_query() 0 34 7
A add_items_query() 0 12 2
A assign_item() 0 20 4
A item_get() 0 4 1
A item_set() 0 4 1
A item_unset() 0 4 1
A is_polymorphic() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Kohana_Jam_Association_Hasmany often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Kohana_Jam_Association_Hasmany, and based on these observations, apply Extract Interface, too.

1
<?php defined('SYSPATH') OR die('No direct script access.');
2
3
/**
4
 * Handles has one to relationships
5
 *
6
 * @package    Jam
7
 * @category   Associations
8
 * @author     Ivan Kerin
9
 * @copyright  (c) 2012 Despark Ltd.
10
 * @license    http://www.opensource.org/licenses/isc-license.txt
11
 */
12
abstract class Kohana_Jam_Association_Hasmany extends Jam_Association_Collection {
13
14
	/**
15
	 * Set this for polymorphic associations, this has to be the name of the opposite belongsto relation,
16
	 * so if the oposite relation was item->parent, then this will have to be 'items' => Jam::association('hasmany', array('as' => 'parent')
17
	 * If this option is set then the foreign_key default becomes "{$as}_id", and polymorphic_key to "{$as}_model"
18
	 * @var string
19
	 */
20
	public $as;
21
22
	/**
23
	 * The field in the opposite model that is used to linking to this one.
24
	 * It defaults to "{$foreign_model}_id", but you can custumize it
25
	 * @var string
26
	 */
27
	public $foreign_key = NULL;
28
29
	/**
30
	 * The field in the opposite model that is used by the polymorphic association to determine the model
31
	 * It defaults to "{$as}_model"
32
	 * @var string
33
	 */
34
	public $polymorphic_key = NULL;
35
36
	/**
37
	 * You can set this to the name of the opposite belongsto relation for optimization purposes
38
	 * @var string
39
	 */
40
	public $inverse_of = NULL;
41
42
	/**
43
	 * Optionally delete the item when it is removed from the association
44
	 * @var string
45
	 */
46
	public $delete_on_remove = NULL;
47
48
	/**
49
	 * Initialize foreign_key, as, and polymorphic_key with default values
50
	 *
51
	 * @param   string  $model
0 ignored issues
show
Bug introduced by
There is no parameter named $model. 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...
52
	 * @param   string  $name
53
	 */
54 39
	public function initialize(Jam_Meta $meta, $name)
55
	{
56 39
		parent::initialize($meta, $name);
57
58 39
		if ( ! $this->foreign_key)
59 39
		{
60 35
			$this->foreign_key = $this->model.'_id';
61 35
		}
62
63
		// Polymorphic associations
64 39
		if ($this->as)
65 39
		{
66 8
			$this->foreign_key = $this->as.'_id';
67 8
			if ( ! $this->polymorphic_key)
68 8
			{
69 8
				$this->polymorphic_key = $this->as.'_model';
70 8
			}
71 8
		}
72 39
	}
73
74
	/**
75
	 * Return a Jam_Query_Builder_Join object to allow a query to join with this association
76
	 * @param  string $alias table name alias
77
	 * @param  string $type  join type (LEFT, NATURAL)
78
	 * @return Jam_Query_Builder_Join
79
	 */
80 9
	public function join($alias, $type = NULL)
81
	{
82 9
		$join = Jam_Query_Builder_Join::factory($alias ? array($this->foreign_model, $alias) : $this->foreign_model, $type)
0 ignored issues
show
Bug introduced by
It seems like $alias ? array($this->fo... : $this->foreign_model can also be of type array<integer,string,{"0":"string","1":"string"}>; however, Kohana_Jam_Query_Builder_Join::factory() does only seem to accept string, 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...
83 9
			->context_model($this->model)
84 9
			->on($this->foreign_key, '=', ':primary_key');
85
86 9
		if ($this->is_polymorphic())
87 9
		{
88 2
			$join->on($this->polymorphic_key, '=', DB::expr(':model', array(':model' => $this->model)));
89 2
		}
90
91 9
		return $join;
92
	}
93
94 10
	public function collection(Jam_Model $model)
95
	{
96 10
		$collection = Jam::all($this->foreign_model);
97
98 10
		$collection->where($this->foreign_key, '=', $model->id());
99
100 10
		if ($this->is_polymorphic())
101 10
		{
102 4
			$collection->where($this->polymorphic_key, '=', $this->model);
103 4
		}
104
105 10
		return $collection;
106
	}
107
108
	/**
109
	 * Assign inverse associations to elements of arrays
110
	 * @param Jam_Validated $model
111
	 * @param mixed         $value
112
	 * @param boolean       $is_changed
113
	 */
114 2
	public function set(Jam_Validated $model, $value, $is_changed)
115
	{
116 2
		if (($this->inverse_of OR $this->as) AND is_array($value))
117 2
		{
118 1
			foreach ($value as & $item)
119
			{
120 1
				if ($item instanceof Jam_Model)
121 1
				{
122 1
					$this->assign_item($item, $model->id(), $this->model, $model);
123 1
				}
124 1
			}
125 1
		}
126
127 2
		return $value;
128
	}
129
130
	/**
131
	 * Before the model is deleted, and the depenedent option is set, remove the dependent models
132
	 * @param  Jam_Model $model
133
	 */
134 5
	public function model_before_delete(Jam_Model $model)
135
	{
136 5
		if (Jam_Association::DELETE === $this->dependent)
137 5
		{
138 2
			foreach ($model->{$this->name} as $item)
139
			{
140
				$item->delete();
141 2
			}
142 5
		} elseif (Jam_Association::ERASE === $this->dependent)
143
		{
144 1
			$query = $this->erase_query($model);
145
			if ($query)
146 1
			{
147 1
				$query->execute();
148 1
			}
149 5
		} elseif (Jam_Association::NULLIFY === $this->dependent)
150
		{
151
			$query = $this->nullify_query($model);
152
			if ($query)
153
			{
154
				$query->execute();
155
			}
156
		}
157 5
	}
158
159
	/**
160
	 * Remove items from this association (withought deleteing it) and persist the data in the database
161
	 * @param  Jam_Validated                $model
162
	 * @param  Jam_Array_Association $collection
163
	 */
164 1
	public function clear(Jam_Validated $model, Jam_Array_Association $collection)
165
	{
166 1
		foreach ($collection as $item)
167
		{
168 1
			$item->{$this->foreign_key} = NULL;
169 1
		}
170
171 1
		parent::clear($model, $collection);
172 1
	}
173
174
	/**
175
	 * Generate a query to delete associated models in the database
176
	 * @param  Jam_Model $model
177
	 * @return Database_Query
178
	 */
179 6
	public function erase_query(Jam_Model $model)
180
	{
181 6
		if (NULL === $model->id())
182 6
		{
183 1
			return NULL;
184
		}
185
186 5
		$query = Jam_Query_Builder_Delete::factory($this->foreign_model)
187 5
			->where($this->foreign_key, '=', $model->id());
188
189 5
		if ($this->is_polymorphic())
190 5
		{
191 1
			$query->where($this->polymorphic_key, '=', $this->model);
192 1
		}
193
194 5
		return $query;
195
	}
196
197
	/**
198
	 * Generate a query to remove models from this association (without deleting them)
199
	 * @param  Jam_Model $model
200
	 * @return Database_Query
201
	 */
202 5
	public function nullify_query(Jam_Model $model)
203
	{
204 5
		if (NULL === $model->id())
205 5
		{
206 1
			return NULL;
207
		}
208
209 4
		$query = Jam_Query_Builder_Update::factory($this->foreign_model)
210 4
			->value($this->foreign_key, NULL)
211 4
			->where($this->foreign_key, '=', $model->id());
212
213 4
		if ($this->is_polymorphic())
214 4
		{
215
			$query
216 1
				->where($this->polymorphic_key, '=', $this->model)
217 1
				->value($this->polymorphic_key, NULL);
218 1
		}
219 4
		return $query;
220
	}
221
222
	/**
223
	 * Generate a query to remove models from the association (without deleting them), for specific ids
224
	 * @param  Jam_Model $model
225
	 * @param  array     $ids
226
	 * @return Database_Query
227
	 */
228
	public function remove_items_query(Jam_Model $model, array $ids)
229
	{
230
		if (TRUE === $this->delete_on_remove OR Jam_Association::DELETE === $this->delete_on_remove)
231
		{
232
			foreach (Jam::all($this->foreign_model)->where_key($ids) as $item )
233
			{
234
				$item->delete();
235
			}
236
			$query = NULL;
237
		}
238
		elseif ($this->delete_on_remove === Jam_Association::ERASE)
239
		{
240
			$query = Jam_Query_Builder_Delete::factory($this->foreign_model)
241
				->where(':primary_key', 'IN', $ids);
242
243
			if ($this->is_polymorphic())
244
			{
245
				$query->value($this->polymorphic_key, NULL);
0 ignored issues
show
Documentation Bug introduced by
The method value does not exist on object<Jam_Query_Builder_Delete>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
246
			}
247
		}
248
		else
249
		{
250
			$query = Jam_Query_Builder_Update::factory($this->foreign_model)
251
				->where(':primary_key', 'IN', $ids)
252
				->value($this->foreign_key, NULL);
253
254
			if ($this->is_polymorphic())
255
			{
256
				$query->value($this->polymorphic_key, NULL);
257
			}
258
		}
259
260
		return $query;
261
	}
262
263
	/**
264
	 * Generate a query to add models from the association (without deleting them), for specific ids
265
	 * @param  Jam_Model $model
266
	 * @param  array     $ids
267
	 * @return Database_Query
268
	 */
269 5
	public function add_items_query(Jam_Model $model, array $ids)
270
	{
271 5
		$query = Jam_Query_Builder_Update::factory($this->foreign_model)
272 5
			->where(':primary_key', 'IN', $ids)
273 5
			->value($this->foreign_key, $model->id());
274
275 5
		if ($this->is_polymorphic())
276 5
		{
277 2
			$query->value($this->polymorphic_key, $this->model);
278 2
		}
279 5
		return $query;
280
	}
281
282
283 8
	protected function assign_item(Jam_Model $item, $foreign_key, $polymorphic_key, $inverse_of)
284
	{
285 8
		$item->{$this->foreign_key} = $foreign_key;
286
287 8
		if ($this->is_polymorphic())
288 8
		{
289 3
			$item->{$this->polymorphic_key} = $polymorphic_key;
290 3
		}
291
292 8
		if ($this->inverse_of)
293 8
		{
294 6
			$item->retrieved($this->inverse_of, $inverse_of);
295 6
		}
296
297 8
		if ($this->as)
298 8
		{
299 3
			$item->retrieved($this->as, $inverse_of);
300 3
		}
301
302 8
	}
303
304
	/**
305
	 * Set the foreign and polymorphic keys on an item when its requested from the associated collection
306
	 *
307
	 * @param  Jam_Model $model
308
	 * @param  Jam_Model $item
309
	 */
310 7
	public function item_get(Jam_Model $model, Jam_Model $item)
311
	{
312 7
		$this->assign_item($item, $model->id(), $this->model, $model);
313 7
	}
314
315
	/**
316
	 * Set the foreign and polymorphic keys on an item when its set to the associated collection
317
	 *
318
	 * @param  Jam_Model $model
319
	 * @param  Jam_Model $item
320
	 */
321 3
	public function item_set(Jam_Model $model, Jam_Model $item)
322
	{
323 3
		$this->assign_item($item, $model->id(), $this->model, $model);
324 3
	}
325
326
	/**
327
	 * Unset the foreign and polymorphic keys on an item when its removed from the associated collection
328
	 *
329
	 * @param  Jam_Model $model
330
	 * @param  Jam_Model $item
331
	 */
332
	public function item_unset(Jam_Model $model, Jam_Model $item)
333
	{
334
		$this->assign_item($item, NULL, NULL, NULL);
335
	}
336
337
	/**
338
	 * See if the association is polymorphic
339
	 * @return boolean
340
	 */
341 43
	public function is_polymorphic()
342
	{
343 43
		return (bool) $this->as;
344
	}
345
} // End Kohana_Jam_Association_HasMany
346