AttributeLoader::getRequiredAttributes()   F
last analyzed

Complexity

Conditions 20
Paths 148

Size

Total Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 420

Importance

Changes 0
Metric Value
cc 20
nc 148
nop 1
dl 0
loc 77
ccs 0
cts 60
cp 0
crap 420
rs 3.7666
c 0
b 0
f 0

How to fix   Long Method    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
2
namespace Elgg;
3
4
/**
5
 * Loads \ElggEntity attributes from DB or validates those passed in via constructor
6
 *
7
 * @access private
8
 *
9
 * @package    Elgg.Core
10
 * @subpackage DataModel
11
 */
12
class AttributeLoader {
13
14
	/**
15
	 * @var array names of attributes in all entities
16
	 *
17
	 * @todo require this to be injected and get it from \ElggEntity
18
	 */
19
	protected static $primary_attr_names = array(
20
		'guid',
21
		'type',
22
		'subtype',
23
		'owner_guid',
24
		'container_guid',
25
		'site_guid',
26
		'access_id',
27
		'time_created',
28
		'time_updated',
29
		'last_action',
30
		'enabled',
31
	);
32
33
	/**
34
	 * @var array names of attributes in all entities that should be stored as integer values
35
	 */
36
	protected static $integer_attr_names = array(
37
		'guid',
38
		'owner_guid',
39
		'container_guid',
40
		'site_guid',
41
		'access_id',
42
		'time_created',
43
		'time_updated',
44
		'last_action',
45
		// \ElggUser
46
		'prev_last_action',
47
		'last_login',
48
		'prev_last_login'
49
	);
50
51
	/**
52
	 * @var array names of attributes in all entities that should be stored as null if empty
53
	 */
54
	protected static $null_attr_names = array(
55
		'name',
56
		'title',
57
		'description',
58
		'url',
59
	);
60
61
	/**
62
	 * @var array names of secondary attributes required for the entity
63
	 */
64
	protected $secondary_attr_names = array();
65
66
	/**
67
	 * @var string entity type (not class) required for fetched primaries
68
	 */
69
	protected $required_type;
70
71
	/**
72
	 * @var array
73
	 */
74
	protected $initialized_attributes;
75
76
	/**
77
	 * @var string class of object being loaded
78
	 */
79
	protected $class;
80
81
	/**
82
	 * @var bool should access control be considered when fetching entity?
83
	 */
84
	public $requires_access_control = true;
85
86
	/**
87
	 * @var callable function used to load attributes from {prefix}entities table
88
	 */
89
	public $primary_loader = 'get_entity_as_row';
90
91
	/**
92
	 * @var callable function used to load attributes from secondary table
93
	 */
94
	public $secondary_loader = '';
95
96
	/**
97
	 * @var callable function used to load all necessary attributes
98
	 */
99
	public $full_loader = '';
100
101
	/**
102
	 * @var array retrieved values that are not attributes
103
	 */
104
	protected $additional_select_values = array();
105
106
	/**
107
	 * Constructor
108
	 *
109
	 * @param string $class             class of object being loaded
110
	 * @param string $required_type     entity type this is being used to populate
111
	 * @param array  $initialized_attrs attributes after initializeAttributes() has been run
112
	 * @throws \InvalidArgumentException
113
	 */
114
	public function __construct($class, $required_type, array $initialized_attrs) {
115
		if (!is_string($class)) {
116
			throw new \InvalidArgumentException('$class must be a class name.');
117
		}
118
		$this->class = $class;
119
120
		if (!is_string($required_type)) {
121
			throw new \InvalidArgumentException('$requiredType must be a system entity type.');
122
		}
123
		$this->required_type = $required_type;
124
125
		$this->initialized_attributes = $initialized_attrs;
126
		$all_attr_names = array_keys($initialized_attrs);
127
		$this->secondary_attr_names = array_diff($all_attr_names, self::$primary_attr_names);
128
	}
129
130
	/**
131
	 * Get primary attributes missing that are missing
132
	 *
133
	 * @param \stdClass $row Database row
134
	 * @return array
135
	 */
136
	protected function isMissingPrimaries($row) {
137
		return array_diff(self::$primary_attr_names, array_keys($row)) !== array();
138
	}
139
140
	/**
141
	 * Get secondary attributes that are missing
142
	 *
143
	 * @param \stdClass $row Database row
144
	 * @return array
145
	 */
146
	protected function isMissingSecondaries($row) {
147
		return array_diff($this->secondary_attr_names, array_keys($row)) !== array();
148
	}
149
150
	/**
151
	 * Check that the type is correct
152
	 *
153
	 * @param \stdClass $row Database row
154
	 * @return void
155
	 * @throws \InvalidClassException
156
	 */
157
	protected function checkType($row) {
158
		if ($row['type'] !== $this->required_type) {
159
			$msg = "GUID:" . $row['guid'] . " is not a valid " . $this->class;
160
			throw new \InvalidClassException($msg);
161
		}
162
	}
163
164
	/**
165
	 * Get values selected from the database that are not attributes
166
	 *
167
	 * @return array
168
	 */
169
	public function getAdditionalSelectValues() {
170
		return $this->additional_select_values;
171
	}
172
	
173
	/**
174
	 * Get all required attributes for the entity, validating any that are passed in. Returns empty array
175
	 * if can't be loaded (Check $failure_reason).
176
	 *
177
	 * This function splits loading between "primary" attributes (those in {prefix}entities table) and
178
	 * "secondary" attributes (e.g. those in {prefix}objects_entity), but can load all at once if a
179
	 * combined loader is available.
180
	 *
181
	 * @param mixed $row a row loaded from DB (array or \stdClass) or a GUID
182
	 * @return array will be empty if failed to load all attributes (access control or entity doesn't exist)
183
	 *
184
	 * @throws \InvalidArgumentException|\LogicException|\IncompleteEntityException
185
	 */
186
	public function getRequiredAttributes($row) {
187
		if (!is_array($row) && !($row instanceof \stdClass)) {
188
			// assume row is the GUID
189
			$row = array('guid' => $row);
190
		}
191
		$row = (array) $row;
192
		if (empty($row['guid'])) {
193
			throw new \InvalidArgumentException('$row must be or contain a GUID');
194
		}
195
196
		$was_missing_primaries = $this->isMissingPrimaries($row);
0 ignored issues
show
Documentation introduced by
$row is of type array<string,?,{"guid":"?"}>, but the function expects a object<stdClass>.

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...
197
		$was_missing_secondaries = $this->isMissingSecondaries($row);
0 ignored issues
show
Documentation introduced by
$row is of type array<string,?,{"guid":"?"}>, but the function expects a object<stdClass>.

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...
198
199
		// some types have a function to load all attributes at once, it should be faster
200
		if (($was_missing_primaries || $was_missing_secondaries) && is_callable($this->full_loader)) {
201
			$fetched = (array) call_user_func($this->full_loader, $row['guid']);
202
			if (!$fetched) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fetched 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...
203
				return array();
204
			}
205
			$row = array_merge($row, $fetched);
206
			$this->checkType($row);
0 ignored issues
show
Documentation introduced by
$row is of type array, but the function expects a object<stdClass>.

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...
207
		} else {
208
			if ($was_missing_primaries) {
209
				if (!is_callable($this->primary_loader)) {
210
					throw new \LogicException('Primary attribute loader must be callable');
211
				}
212
				if ($this->requires_access_control) {
213
					$fetched = (array) call_user_func($this->primary_loader, $row['guid']);
214
				} else {
215
					$ignoring_access = elgg_set_ignore_access();
216
					$fetched = (array) call_user_func($this->primary_loader, $row['guid']);
217
					elgg_set_ignore_access($ignoring_access);
218
				}
219
				if (!$fetched) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fetched 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...
220
					return array();
221
				}
222
				$row = array_merge($row, $fetched);
223
			}
224
225
			// We must test type before trying to load the secondaries so that InvalidClassException
226
			// gets thrown. Otherwise the secondary loader will fail and return false.
227
			$this->checkType($row);
0 ignored issues
show
Documentation introduced by
$row is of type array, but the function expects a object<stdClass>.

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...
228
229
			if ($was_missing_secondaries) {
230
				if (!is_callable($this->secondary_loader)) {
231
					throw new \LogicException('Secondary attribute loader must be callable');
232
				}
233
				$fetched = (array) call_user_func($this->secondary_loader, $row['guid']);
234
				if (!$fetched) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fetched 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...
235
					throw new \IncompleteEntityException("Secondary loader failed to return row for {$row['guid']}");
236
				}
237
				$row = array_merge($row, $fetched);
238
			}
239
		}
240
241
		$row = $this->filterAddedColumns($row);
242
243
		$row['subtype'] = (int)$row['subtype'];
244
245
		// set to null when reading empty value, to match default empty value; See #5456
246
		foreach (self::$null_attr_names as $key) {
247
			if (isset($row[$key]) && !$row[$key]) {
248
				$row[$key] = null;
249
			}
250
		}
251
252
		// Note: If there are still missing attributes, we're running on a 1.7 or earlier schema. We let
253
		// this pass so the upgrades can run.
254
255
		// guid needs to be an int  https://github.com/elgg/elgg/issues/4111
256
		foreach (self::$integer_attr_names as $key) {
257
			if (isset($row[$key])) {
258
				$row[$key] = (int) $row[$key];
259
			}
260
		}
261
		return $row;
262
	}
263
264
	/**
265
	 * Filter non-attribute keys into $this->additional_select_values
266
	 *
267
	 * @param array $row All columns from the query
268
	 * @return array Columns acceptable for the entity's attributes
269
	 */
270
	protected function filterAddedColumns($row) {
271
		// make an array with keys as acceptable attribute names
272
		$acceptable_attrs = self::$primary_attr_names;
273
		array_splice($acceptable_attrs, count($acceptable_attrs), 0, $this->secondary_attr_names);
274
		$acceptable_attrs = array_combine($acceptable_attrs, $acceptable_attrs);
275
276
		foreach ($row as $key => $val) {
277
			if (!isset($acceptable_attrs[$key])) {
278
				$this->additional_select_values[$key] = $val;
279
				unset($row[$key]);
280
			}
281
		}
282
		return $row;
283
	}
284
}
285
286