Completed
Push — master ( 3239e9...e1b4b8 )
by Georgi
27s queued 16s
created

Kohana_Jam_Association_Belongsto::set()   B

Complexity

Conditions 11
Paths 24

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 14
cts 14
cp 1
rs 7.3166
c 0
b 0
f 0
cc 11
nc 24
nop 3
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php defined('SYSPATH') OR die('No direct script access.');
2
/**
3
 * Handles belongs to relationships
4
 *
5
 * @package    Jam
6
 * @category   Associations
7
 * @author     Ivan Kerin
8
 * @copyright  (c) 2012 Despark Ltd.
9
 * @license    http://www.opensource.org/licenses/isc-license.txt
10
 */
11
abstract class Kohana_Jam_Association_Belongsto extends Jam_Association {
12
13
	/**
14
	 * Indicates whether this is a polymorphic association. Will add the polymorphic field,
15
	 * named <name>_model, if you set this as a string you can change the name of the field to it.
16
	 * @var boolean|string
17
	 */
18
	public $polymorphic = FALSE;
19
20
	/**
21
	 * This will be set to the polymorphic model column automatically if nothing is set there
22
	 * @var model
23
	 */
24
	public $polymorphic_default_model = NULL;
25
26
	/**
27
	 * The name of the actual field holding the id of the associated model. Defaults to
28
	 * <name>_id
29
	 * @var string
30
	 */
31
	public $foreign_key = NULL;
32
33
	public $inverse_of = NULL;
34
35
	public $count_cache = NULL;
36
37
	public $field_options = array();
38
39
	protected $_default_field_options = array(
40
		'default' => NULL,
41
		'allow_null' => TRUE,
42
		'convert_empty' => TRUE,
43
	);
44
45
	/**
46
	 * Automatically sets foreign to sensible defaults.
47
	 *
48
	 * @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...
49
	 * @param   string  $name
50
	 * @return  void
51
	 */
52 48
	public function initialize(Jam_Meta $meta, $name)
53
	{
54 48
		parent::initialize($meta, $name);
55
56 48
		if ( ! $this->foreign_key)
57
		{
58 45
			$this->foreign_key = $name.'_id';
59
		}
60
61 48
		if ($this->foreign_key === $name)
62 1
			throw new Kohana_Exception('In association ":name" for model ":model" - invalid foreign_key name. Field and Association cannot be the same name', array(
63 1
					':model' => $this->model,
64 1
					':name' => $name,
65
				));
66
67 47
		$meta->field($this->foreign_key, Jam::field('integer', array_merge($this->_default_field_options, $this->field_options)));
68
69 47
		if ($this->is_polymorphic())
70
		{
71 21
			if ( ! is_string($this->polymorphic))
72
			{
73 18
				$this->polymorphic = $name.'_model';
74
			}
75
76 21
			$meta->field($this->polymorphic, Jam::field('string', array('convert_empty' => TRUE)));
77
		}
78 32
		elseif ( ! $this->foreign_model)
79
		{
80 29
			$this->foreign_model = $name;
81
		}
82
83 47
		if ($this->count_cache)
84
		{
85 2
			if ($this->is_polymorphic())
86 1
				throw new Kohana_Exception('Cannot use count cache on polymorphic associations');
87
88 1
			if ($this->count_cache === TRUE)
89
			{
90 1
				$this->count_cache = Inflector::plural($this->model).'_count';
91
			}
92
		}
93 46
	}
94
95
	/**
96
	 * Load associated model (from database or after deserialization)
97
	 * @param  Jam_Validated $model
98
	 * @param  mixed         $value
99
	 * @return Jam_Model
100
	 */
101 9
	public function load_fields(Jam_Validated $model, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $model 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...
102
	{
103 9
		if (is_array($value))
104
		{
105 9
			$value = Jam::build($this->foreign_model)->load_fields($value);
106
		}
107
108 9
		return $value;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $value; (object|integer|double|string|null|boolean) is incompatible with the return type documented by Kohana_Jam_Association_Belongsto::load_fields of type Jam_Model|null.

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...
109
	}
110
111
	/**
112
	 * Return a Jam_Query_Builder_Join object to allow a query to join with this association
113
	 * You can join polymorphic association only when you pass an alias, wich will be used as the
114
	 * name of the model to match to the polymorphic_key
115
	 *
116
	 * @param  string $alias table name alias
117
	 * @param  string $type  join type (LEFT, NATURAL)
118
	 * @return Jam_Query_Builder_Join
119
	 */
120 18
	public function join($alias, $type = NULL)
121
	{
122 18
		if ($this->is_polymorphic())
123
		{
124 2
			$foreign_model = $alias;
125
126 2
			if ( ! $foreign_model)
127
				throw new Kohana_Exception('Jam does not join automatically polymorphic belongsto associations!');
128
129 2
			$join = new Jam_Query_Builder_Join($foreign_model, $type);
130 2
			$join->on(DB::expr(':model', array(':model' => $foreign_model)), '=', $this->polymorphic);
131
		}
132
		else
133
		{
134 16
			$join = new Jam_Query_Builder_Join($alias ? array($this->foreign_model, $alias) : $this->foreign_model, $type);
135
		}
136
137
		return $join
138 18
			->context_model($this->model)
139 18
			->on(':primary_key', '=', $this->foreign_key);
140
	}
141
142
	/**
143
	 * Get the belonging model for this association using the foreign key,
144
	 * if the data was changed, use the key from the changed data.
145
	 * Assign inverse_of
146
	 *
147
	 * @param  Jam_Validated $model
148
	 * @param  mixed         $value      changed data
149
	 * @param  boolean       $is_changed
150
	 * @return Jam_Model
151
	 */
152 21
	public function get(Jam_Validated $model, $value, $is_changed)
153
	{
154 21
		if ($is_changed)
155
		{
156 16
			if ($value instanceof Jam_Validated OR ! $value)
157 9
				return $value;
158
159 7
			$key = Jam_Association::primary_key($this->foreign_model($model), $value);
0 ignored issues
show
Compatibility introduced by
$model of type object<Jam_Validated> is not a sub-type of object<Jam_Model>. It seems like you assume a child class of the class Jam_Validated to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
160
		}
161
		else
162
		{
163 5
			$key = $model->{$this->foreign_key};
164
		}
165
166 12
		if ($key)
167
		{
168 11
			$item = $this->_find_item($this->foreign_model($model), $key);
0 ignored issues
show
Compatibility introduced by
$model of type object<Jam_Validated> is not a sub-type of object<Jam_Model>. It seems like you assume a child class of the class Jam_Validated to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
169
		}
170 1
		elseif (is_array($value))
171
		{
172
			$item = Jam::build($this->foreign_model($model), $value);
0 ignored issues
show
Compatibility introduced by
$model of type object<Jam_Validated> is not a sub-type of object<Jam_Model>. It seems like you assume a child class of the class Jam_Validated to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
173
		}
174
		else
175
		{
176 1
			$item = NULL;
177
		}
178
179
180 12
		if ($item)
181
		{
182 11
			if (is_array($value) AND Jam_Association::is_changed($value))
183
			{
184 3
				$item->set($value);
185
			}
186
187 11
			if ($item instanceof Jam_Model AND $this->inverse_of AND $item->meta()->association($this->inverse_of) instanceof Jam_Association_Hasone)
188
			{
189 1
				$item->retrieved($this->inverse_of, $model);
190
			}
191
		}
192
193 12
		return $item;
194
	}
195
196
	/**
197
	 * Set releated model, by assigning foreign key for this model
198
	 * @param Jam_Validated $model
199
	 * @param mixed         $value
200
	 * @param boolean       $is_changed
201
	 */
202 28
	public function set(Jam_Validated $model, $value, $is_changed)
203
	{
204 28
		if ($this->polymorphic_default_model AND ! $model->{$this->polymorphic})
205
		{
206 2
			$model->{$this->polymorphic} = $this->polymorphic_default_model;
207
		}
208
209 28
		if (is_array($value) AND $this->is_polymorphic())
210
		{
211 3
			$model->{$this->polymorphic} = key($value);
212 3
			$value = current($value);
213
		}
214 25
		elseif ($value instanceof Jam_Model)
215
		{
216 17
			$model->{$this->polymorphic} = $value->meta()->model();
217
		}
218
219 28
		$key = Jam_Association::primary_key($this->foreign_model($model), $value);
0 ignored issues
show
Compatibility introduced by
$model of type object<Jam_Validated> is not a sub-type of object<Jam_Model>. It seems like you assume a child class of the class Jam_Validated to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
220
221 28
		if (is_numeric($key) OR $key === NULL)
222
		{
223 27
			$model->{$this->foreign_key} = $key;
224
		}
225
226 28
		if ($value instanceof Jam_Model AND $this->inverse_of AND $value->meta()->association($this->inverse_of) instanceof Jam_Association_Hasone)
227
		{
228 1
			$value->retrieved($this->inverse_of, $model);
229
		}
230
231 28
		return $value;
232
	}
233
234 2
	public function build(Jam_Validated $model, array $attributes = NULL)
235
	{
236 2
		$foreign = Jam::meta($this->foreign_model($model));
0 ignored issues
show
Compatibility introduced by
$model of type object<Jam_Validated> is not a sub-type of object<Jam_Model>. It seems like you assume a child class of the class Jam_Validated to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
237
238 2
		if ( ! $foreign)
239 1
			return NULL;
240
241 2
		$item = Jam::build($foreign->model(), $attributes);
242
243 2
		if ($this->inverse_of AND $foreign->association($this->inverse_of) instanceof Jam_Association_Hasone)
244
		{
245 1
			$item->retrieved($this->inverse_of, $model);
246
		}
247
248 2
		return $item;
249
	}
250
251
	/**
252
	 * Perform validation on the belonging model, if it was changed.
253
	 * @param  Jam_Model      $model
254
	 * @param  Jam_Event_Data $data
255
	 * @param  array         $changed
256
	 */
257 27
	public function model_after_check(Jam_Model $model, Jam_Event_Data $data, $changed)
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
258
	{
259 27
		if ($value = Arr::get($changed, $this->name) AND Jam_Association::is_changed($value))
260
		{
261 4
			if ( ! $model->{$this->name}->is_validating() AND ! $model->{$this->name}->check())
262
			{
263
				$model->errors()->add($this->name, 'association', array(':errors' => $model->{$this->name}->errors()));
264
			}
265
		}
266 27
	}
267
268
	/**
269
	 * Save the related model before the main model, because we'll need the id to assign to the foreign key
270
	 * Only save related model if it has been changed, and is not in a process of saving itself
271
	 * @param  Jam_Model      $model
272
	 * @param  Jam_Event_Data $data
273
	 * @param  boolean        $changed
274
	 */
275 22
	public function model_before_save(Jam_Model $model, Jam_Event_Data $data, $changed)
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
276
	{
277 22
		if ($value = Arr::get($changed, $this->name))
0 ignored issues
show
Documentation introduced by
$changed is of type boolean, but the function expects a array.

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...
278
		{
279 4
			if (Jam_Association::is_changed($value) AND ($item = $model->{$this->name}))
280
			{
281 2
				if ( ! $item->is_saving())
282
				{
283 2
					$item->save();
284
				}
285 2
				$this->set($model, $item, TRUE);
286
			}
287
			else
288
			{
289 2
				$this->set($model, $value, TRUE);
290
			}
291
		}
292 22
	}
293
294
295
	/**
296
	 * If we're using count_cache, increment the count_cache field on the foreign model
297
	 * @param  Jam_Model $model
298
	 */
299 16
	public function model_after_create(Jam_Model $model)
300
	{
301 16
		if ($this->count_cache AND $model->{$this->foreign_key})
302
		{
303
			Jam_Countcache::increment($this->foreign_model, $this->count_cache, $model->{$this->foreign_key});
304
		}
305 16
	}
306
307
	/**
308
	 * Delete related model if it has been assigned as dependent
309
	 * If dependent is Jam_Association::DELETE - execute the delete method (and all events)
310
	 * IF dependent is Jam_Association::ERASE - simply remove from database without executing any events (faster)
311
	 * @param  Jam_Model $model
312
	 */
313 9
	public function model_before_delete(Jam_Model $model)
314
	{
315 9
		if ($this->dependent == Jam_Association::DELETE AND $model->{$this->name})
316
		{
317
			$model->{$this->name}->delete();
318
		}
319 9
		elseif ($this->dependent == Jam_Association::ERASE)
320
		{
321
			$this->_delete_item($this->foreign_model($model), $model->{$this->foreign_key});
322
		}
323 9
	}
324
325
	/**
326
	 * If we're using count_cache, decrement the count_cache field on the foreign model
327
	 * @param  Jam_Model $model
328
	 */
329 9
	public function model_after_delete(Jam_Model $model)
330
	{
331 9
		if ($this->count_cache AND $model->{$this->foreign_key})
332
		{
333
			Jam_Countcache::decrement($this->foreign_model, $this->count_cache, $model->{$this->foreign_key});
334
		}
335 9
	}
336
337
	/**
338
	 * Check if association is polymophic
339
	 * @return boolean
340
	 */
341 77
	public function is_polymorphic()
342
	{
343 77
		return (bool) $this->polymorphic;
344
	}
345
346
	/**
347
	 * Get the foreign model, if its a polymorphic, use the polymorphic field (e.g. item_model is the polymorphic field, then it's contents will be used)
348
	 * @param  Jam_Model $model
349
	 * @return string
350
	 */
351 40
	public function foreign_model(Jam_Model $model)
352
	{
353 40
		return $this->is_polymorphic() ? ($model->{$this->polymorphic} ?: $this->polymorphic_default_model) : $this->foreign_model;
354
	}
355
356
	/**
357
	 * Get an item based on a unique key from the database
358
	 * @param  string $foreign_model
359
	 * @param  string $key
360
	 * @return Jam_Model
361
	 */
362 2
	protected function _find_item($foreign_model, $key)
363
	{
364 2
		if ( ! $foreign_model)
365
			return NULL;
366
367 2
		return Jam::find($foreign_model, $key);
368
	}
369
370
	/**
371
	 * Delete an item with a specific key from the database
372
	 * @param  string $foreign_model
373
	 * @param  string $key
374
	 * @return Database_Result
375
	 */
376
	protected function _delete_item($foreign_model, $key)
377
	{
378
		if ( ! $foreign_model)
379
			return NULL;
380
381
		return Jam::delete($foreign_model)->where_key($key)->execute();
382
	}
383
384
}
385