Kohana_Jam_Association_Hasone::build()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php defined('SYSPATH') OR die('No direct script access.');
2
/**
3
 * Handles has one 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_Hasone extends Jam_Association {
12
13
	/**
14
	 * Set this for polymorphic association, this has to be the name of the opposite belongsto relation,
15
	 * so if the oposite relation was item->parent, then this will have to be 'ites' => Jam::association('hasone, array('as' => 'parent')
16
	 * If this option is set then the foreign_key default becomes "{$as}_id", and polymorphic_key to "{$as}_model"
17
	 * @var string
18
	 */
19
	public $as = NULL;
20
21
	/**
22
	 * The foreign key
23
	 * @var string
24
	 */
25
	public $foreign_key = NULL;
26
27
	public $inverse_of = NULL;
28
29
	public $polymorphic_key = NULL;
30
31
	/**
32
	 * Automatically sets foreign to sensible defaults.
33
	 *
34
	 * @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...
35
	 * @param   string  $name
36
	 * @return  void
37
	 */
38 23
	public function initialize(Jam_Meta $meta, $name)
39
	{
40 23
		parent::initialize($meta, $name);
41
42 23
		if ( ! $this->foreign_model)
43
		{
44 20
			$this->foreign_model = $name;
45
		}
46
47 23
		if ( ! $this->foreign_key)
48
		{
49 22
			$this->foreign_key = $this->model.'_id';
50
		}
51
52
		// Polymorphic associations
53 23
		if ($this->as)
54
		{
55 12
			$this->foreign_key = $this->as.'_id';
56 12
			if ( ! $this->polymorphic_key)
57
			{
58 12
				$this->polymorphic_key = $this->as.'_model';
59
			}
60
		}
61 23
	}
62
63
	/**
64
	 * Load associated model (from database or after deserialization)
65
	 * @param  Jam_Validated $model
66
	 * @param  mixed         $value
67
	 * @return Jam_Model
68
	 */
69 1
	public function load_fields(Jam_Validated $model, $value)
70
	{
71 1
		if (is_array($value))
72
		{
73 1
			$value = Jam::build($this->foreign_model)->load_fields($value);
74
		}
75
76 1
		if ($value instanceof Jam_Model AND $this->inverse_of)
77
		{
78
			$value->retrieved($this->inverse_of, $model);
79
		}
80
81 1
		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_Hasone::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...
82
	}
83
84
	/**
85
	 * Return a Jam_Query_Builder_Join object to allow a query to join with this association
86
	 *
87
	 * @param  string $alias table name alias
88
	 * @param  string $type  join type (LEFT, NATURAL)
89
	 * @return Jam_Query_Builder_Join
90
	 */
91 5
	public function join($alias, $type = NULL)
92
	{
93 5
		$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...
94 5
			->context_model($this->model)
95 5
			->on($this->foreign_key, '=', ':primary_key');
96
97 5
		if ($this->is_polymorphic())
98
		{
99 2
			$join->on($this->polymorphic_key, '=', DB::expr(':model', array(':model' => $this->model)));
100
		}
101
102 5
		return $join;
103
	}
104
105
	/**
106
	 * Get the belonging model for this association using the foreign key,
107
	 * if the data was changed, use the key from the changed data.
108
	 * Assign inverse_of
109
	 *
110
	 * @param  Jam_Validated $model
111
	 * @param  mixed         $value      changed data
112
	 * @param  boolean       $is_changed
113
	 * @return Jam_Model
114
	 */
115 6
	public function get(Jam_Validated $model, $value, $is_changed)
116
	{
117 6
		if ($is_changed)
118
		{
119 4
			if ($value instanceof Jam_Validated OR ! $value)
120 3
				return $value;
121
122 2
			$key = Jam_Association::primary_key($this->foreign_model, $value);
123
124 2
			if ($key)
125
			{
126 2
				$item = $this->_find_item($this->foreign_model, $key);
127
			}
128
			elseif (is_array($value))
129
			{
130
				$item = Jam::build($this->foreign_model, $value);
131
			}
132
			else
133
			{
134
				$item = NULL;
135
			}
136
137 2
			if ($item AND is_array($value))
138
			{
139 2
				$item->set($value);
140
			}
141
		}
142
		else
143
		{
144 4
			$item = $this->_find_item($this->foreign_model, $model);
145
		}
146
147 4
		return $this->set($model, $item, $is_changed);
148
	}
149
150 7
	public function set(Jam_Validated $model, $value, $is_changed)
151
	{
152 7
		if ($value instanceof Jam_Model)
153
		{
154 4
			if ($this->is_polymorphic())
155
			{
156 1
				$value->{$this->polymorphic_key} = $model->meta()->model();
157
			}
158
159 4
			if ($model->loaded())
0 ignored issues
show
Documentation Bug introduced by
The method loaded does not exist on object<Jam_Validated>? 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...
160
			{
161 3
				$value->{$this->foreign_key} = $model->id();
162
			}
163
164 4
			if ($this->as)
165
			{
166 1
				$value->retrieved($this->as, $model);
167
			}
168
169 4
			if ($this->inverse_of)
170
			{
171 3
				$value->retrieved($this->inverse_of, $model);
172
			}
173
		}
174
175 7
		return $value;
176
	}
177
178 1
	public function build(Jam_Validated $model, array $attributes = NULL)
179
	{
180 1
		$item = Jam::build($this->foreign_model, $attributes);
181
182 1
		$this->set($model, $item, TRUE);
183
184 1
		return $item;
185
	}
186
187
	/**
188
	 * Perform validation on the belonging model, if it was changed.
189
	 * @param  Jam_Model      $model
190
	 * @param  Jam_Event_Data $data
191
	 * @param  array         $changed
192
	 */
193 8
	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...
194
	{
195 8
		if (($value = Arr::get($changed, $this->name)) AND Jam_Association::is_changed($value))
196
		{
197 2
			if ( ! $model->{$this->name}->is_validating() AND ! $model->{$this->name}->check())
198
			{
199
				$model->errors()->add($this->name, 'association', array(':errors' => $model->{$this->name}->errors()));
200
			}
201
		}
202 8
	}
203
204
	/**
205
	 * Save the related model after the main model, if it was changed
206
	 * Only save related model if it has been changed, and is not in a process of saving itself
207
	 *
208
	 * @param  Jam_Model      $model
209
	 * @param  Jam_Event_Data $data
210
	 * @param  boolean        $changed
211
	 */
212 7
	public function model_after_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...
213
	{
214 7
		$nullify_query = $this->update_query($model, NULL, NULL);
215
216 7
		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...
217
		{
218 1
			if (Jam_Association::is_changed($value) AND ($item = $model->{$this->name}))
219
			{
220 1
				if ( ! $item->is_saving())
221
				{
222 1
					$this->set($model, $item, TRUE)->save();
223
				}
224 1
				if ($item->id())
225
				{
226 1
					$nullify_query->where('id', '!=', $item->id())->execute();
227
				}
228
			}
229
			else
230
			{
231
				$key = Jam_Association::primary_key($this->foreign_model, $value);
232
233
				$query = Jam_Query_Builder_Update::factory($this->foreign_model)
234
					->where(':unique_key', '=', $key)
235
					->value($this->foreign_key, $model->id());
236
237
				if ($this->is_polymorphic())
238
				{
239
					$query
240
						->value($this->polymorphic_key, $model->meta()->model());
241
				}
242
				$nullify_query->execute();
243 1
				$query->execute();
244
			}
245
		}
246 7
		elseif (array_key_exists($this->name, $changed))
247
		{
248 1
			$nullify_query->execute();
249
		}
250 7
	}
251
252
	/**
253
	 * Delete related model if it has been assigned as dependent
254
	 * If dependent is Jam_Association::DELETE - execute the delete method (and all events)
255
	 * IF dependent is Jam_Association::ERASE - simply remove from database without executing any events (faster)
256
	 * @param  Jam_Model $model
257
	 */
258 3
	public function model_before_delete(Jam_Model $model)
259
	{
260 3
		if (Jam_Association::DELETE === $this->dependent)
261
		{
262 1
			if ($model->{$this->name})
263
			{
264 1
				$model->{$this->name}->delete();
265
			}
266
		}
267 2
		elseif (Jam_Association::ERASE === $this->dependent)
268
		{
269
			$this->query_builder('delete', $model)->execute();
270
		}
271 2
		elseif (Jam_Association::NULLIFY === $this->dependent)
272
		{
273
			$this->update_query($model, NULL, NULL)->execute();
274
		}
275 3
	}
276
277
	/**
278
	 * See if the association is polymorphic
279
	 * @return boolean
280
	 */
281 24
	public function is_polymorphic()
282
	{
283 24
		return (bool) $this->as;
284
	}
285
286 3
	protected function _find_item($foreign_model, $key)
287
	{
288 3
		if ( ! $key)
289
			return;
290
291 3
		if ($key instanceof Jam_Model)
292
		{
293 3
			if ( ! $key->loaded())
294
				return;
295
296 3
			$query = $this->query_builder('all', $key);
297
		}
298
		else
299
		{
300 1
			$query = new Jam_Query_Builder_Collection($foreign_model);
301
			$query
302 1
				->where(':unique_key', '=', $key)
303 1
				->limit(1);
304
		}
305
306 3
		return $query->current();
307
	}
308
309 11
	public function query_builder($type, Jam_Model $model)
310
	{
311 11
		$query = call_user_func("Jam::{$type}", $this->foreign_model)
312 11
			->where($this->foreign_key, '=', $model->id());
313
314 11
		if ($this->is_polymorphic())
315
		{
316 4
			$query->where($this->polymorphic_key, '=', $model->meta()->model());
317
		}
318
319 11
		return $query;
320
	}
321
322 9
	public function update_query(Jam_Model $model, $new_id, $new_model)
323
	{
324 9
		$query = $this->query_builder('update', $model)
325 9
			->value($this->foreign_key, $new_id);
326
327 9
		if ($this->is_polymorphic())
328
		{
329 3
			$query->value($this->polymorphic_key, $new_model);
330
		}
331
332 9
		return $query;
333
	}
334
}
335