PhoebeObject   F
last analyzed

Complexity

Total Complexity 88

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Test Coverage

Coverage 81.59%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 88
eloc 181
dl 0
loc 458
rs 2
c 1
b 0
f 0
ccs 164
cts 201
cp 0.8159

24 Methods

Rating   Name   Duplication   Size   Complexity  
B createDdsObjectStubs() 0 35 9
A initialiseFromDds() 0 8 3
A setDataSources() 0 7 3
A updateFromDds() 0 6 2
A getDataSourceObject() 0 3 2
A editObject() 0 13 3
A getDataSources() 0 4 2
A updateDataFromDds() 0 18 6
A objectCanBeSaved() 0 23 5
A getDataSourceDefinition() 0 4 2
A getAllowedFieldsForClass() 0 11 4
B saveToDaedalus() 0 18 7
B extractObjects() 0 52 10
A isVolatile() 0 3 1
B setObjectUuids() 0 22 7
A getRenderables() 0 7 2
A createRenderablesFromDdsData() 0 13 4
A getRenderable() 0 4 1
A getDds() 0 5 2
A getDefinition() 0 7 2
A getFormDefinition() 0 3 1
A setObjectDataFromDataSource() 0 9 4
A getClass() 0 5 2
A saveLinkedObjects() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like PhoebeObject 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.

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 PhoebeObject, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.newicon.net/neon
4
 * @copyright Copyright (c) 2018- Newicon Ltd
5
 * @license http://www.newicon.net/neon/license/
6
 */
7
8
namespace neon\phoebe\services\adapters\appforms;
9
10
use neon\phoebe\services\adapters\common\PhoebeObjectBase;
11
use neon\phoebe\interfaces\forms\IPhoebeFormObject;
12
use neon\core\helpers\Hash;
13
14
/**
15
 * Provides the applicationForm phoebe object
16
 */
17
class PhoebeObject extends PhoebeObjectBase
18
implements IPhoebeFormObject
19
{
20
	/**
21
	 * The set of dataSource Ids as 'key'=>'uuid'
22
	 * @var array
23
	 */
24
	protected $dataSourceIds = [];
25
26
	/**
27
	 * The set of data source objects
28
	 * @var array
29
	 */
30
	protected $dataSourceObjects = [];
31
32
	/**
33
	 * The set of new objects in the data
34
	 * @var array
35
	 */
36
	private $newObjects = [];
37
38
	/**
39
	 * @inheritdoc
40
	 */
41 2
	public function editObject($changes)
42
	{
43
		// check if there's anything to do
44 2
		if (empty($changes) || $changes == $this->data)
45
			return true;
46
47
		// extract the data and save appropriately to
48
		// the associated database tables ... if any
49 2
		$this->newObjects = [];
50 2
		$this->setObjectUuids($changes);
51 2
		$this->saveToDaedalus($changes);
52
		// now do the basic Phoebe saving
53 2
		return parent::editObject($changes);
54
	}
55
56
	/**
57
	 * @inheritdoc
58
	 */
59
	public function setDataSources($sources)
60
	{
61
		foreach ($sources as $key=>$value) {
62
			if (empty($value))
63
				unset($sources[$key]);
64
		}
65
		$this->dataSourceIds = $sources;
66
	}
67
68
	/**
69
	 * @inheritdoc
70
	 */
71
	public function updateFromDds()
72
	{
73
		if ($this->isVolatile()) {
74
			$data = $this->data;
75
			$this->updateDataFromDds($data);
0 ignored issues
show
Bug introduced by
$data of type array is incompatible with the type neon\phoebe\services\adapters\appforms\type expected by parameter $objects of neon\phoebe\services\ada...ct::updateDataFromDds(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

75
			$this->updateDataFromDds(/** @scrutinizer ignore-type */ $data);
Loading history...
76
			$this->data = $data;
77
		}
78
	}
79
80
81
	/**
82
	 * @inheritdoc
83
	 */
84 2
	public function initialiseFromDds($dataObjects)
85
	{
86 2
		if (!$this->data && $this->isVolatile()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->data 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...
87
			// create some stubs, update from Dds, and then create the data hiearchy
88 2
			$ddsObjects = $this->createDdsObjectStubs($dataObjects);
89 2
			$this->updateDataFromDds($ddsObjects);
0 ignored issues
show
Bug introduced by
$ddsObjects of type array is incompatible with the type neon\phoebe\services\adapters\appforms\type expected by parameter $objects of neon\phoebe\services\ada...ct::updateDataFromDds(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
			$this->updateDataFromDds(/** @scrutinizer ignore-type */ $ddsObjects);
Loading history...
90 2
			$definition = $this->getDefinition();
91 2
			$this->data = $this->createRenderablesFromDdsData($definition['rootNode'], $ddsObjects);
92
		}
93 2
	}
94
95
	/**
96
	 * Get hold of any data source objects
97
	 */
98 2
	protected function getDataSources()
99
	{
100 2
		foreach ($this->dataSourceIds as $key=>$id)
101
			$this->dataSourceObjects[$key] = $this->getDds()->getObject($id);
102 2
	}
103
104
	/**
105
	 * Update all of the data from the database.
106
	 *
107
	 * @param type $objects
0 ignored issues
show
Bug introduced by
The type neon\phoebe\services\adapters\appforms\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
108
	 */
109 2
	protected function updateDataFromDds(&$objects, $isTree=true)
110
	{
111
		// It may be better to flatten the data and make a more intelligent
112
		// set of selects from the database, or use the idea of requests and
113
		// do this in two passes
114 2
		$dds = neon('dds')->getIDdsObjectManagement();
115 2
		foreach($objects as &$object) {
116 2
			if (!empty($object['_uuid'])) {
117
				// update the object definition to pick up any new fields from the definition
118 2
				$object = array_merge($this->getAllowedFieldsForClass($object['_classType']), $object);
119
				// now update the object with the data from daedalus
120 2
				$data = $dds->getObject($object['_uuid']);
121 2
				if ($data)
122 2
					$object = array_replace($object, array_intersect_key($data, $object));
123
			}
124
			// recursively update if this is in a tree structure
125 2
			if ($isTree && is_array($object)) {
126 2
				$this->updateDataFromDds($object, true);
0 ignored issues
show
Bug introduced by
$object of type array is incompatible with the type neon\phoebe\services\adapters\appforms\type expected by parameter $objects of neon\phoebe\services\ada...ct::updateDataFromDds(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

126
				$this->updateDataFromDds(/** @scrutinizer ignore-type */ $object, true);
Loading history...
127
			}
128
		}
129 2
	}
130
131
	/**
132
	 * Set uuids on any new objects.
133
	 * @param array $objects
134
	 */
135 2
	protected function setObjectUuids(&$objects)
136
	{
137 2
		$renderables = $this->getRenderables();
138 2
		$renderableKeys = array_keys($renderables);
139 2
		foreach ($objects as &$object) {
140 2
			if (!empty($object['_renderable'])) {
141 2
				if ($this->objectCanBeSaved($object)) {
142 2
					$isNew = empty($object['_uuid']);
143 2
					if ($isNew) {
144
						$object['_uuid'] = Hash::uuid64();
145
						$this->newObjects[] = $object['_uuid'];
146
					}
147
					// find any subRenderables in the definition
148 2
					$subRenderables = array_intersect($renderableKeys, array_keys($object));
149 2
					foreach ($subRenderables as $subRenderable) {
150 2
						$this->setObjectUuids($object[$subRenderable]);
151
					}
152
				}
153
			}
154
			// and if not go down the next depth
155
			else if (is_array($object)) {
156
				$this->setObjectUuids($object);
157
			}
158
		}
159 2
	}
160
161
	/**
162
	 * Save the changes to Daedalus. The data will be altered during
163
	 * this call as required to match data with Daedalus
164
	 * @param array $data
165
	 */
166 2
	protected function saveToDaedalus($data)
167
	{
168 2
		$objectsByClassType = $this->extractObjects($data);
169 2
		$relations = !empty($objectsByClassType['__relations']) ? $objectsByClassType['__relations'] : [];
170 2
		unset($objectsByClassType['__relations']);
171 2
		$dds = $this->getDds();
172 2
		foreach ($objectsByClassType as $classType => $objects) {
173 2
			if (!empty($objects['new'])) {
174
				$dds->addObjects($classType, $objects['new']);
175
			}
176 2
			if (!empty($objects['existing'])) {
177 2
				foreach ($objects['existing'] as $object) {
178 2
					$dds->editObject($object['_uuid'], $object);
179
				}
180
			}
181
		}
182 2
		if (count($relations))
183 2
			$this->saveLinkedObjects($relations);
184 2
	}
185
186
	/**
187
	 * Go through all of the data and collate together the objects
188
	 * by the class id involved. The objects will have their uuid's set
189
	 * if these were not already set.
190
	 *
191
	 * @param array &$objects
192
	 * @return array
193
	 */
194 2
	protected function extractObjects(&$objects, &$extractedObjectIds=null)
195
	{
196
		//
197
		// TODO - this code is particularly messy - refactor
198
		//
199 2
		$extracted = [];
200 2
		$extractedObjectIds = [];
201 2
		$renderables = $this->getRenderables();
202 2
		$renderableKeys = array_keys($renderables);
203 2
		$this->getDataSources();
204 2
		foreach ($objects as $key => &$object) {
205
			// see if this is a class data object
206 2
			if (!empty($object['_renderable'])) {
207 2
				if ($this->objectCanBeSaved($object)) {
208 2
					$this->setObjectDataFromDataSource($object);
209 2
					$objId = $object['_uuid'];
210
					// find any subRenderables in the definition
211 2
					$subRenderables = array_intersect($renderableKeys, array_keys($object));
212 2
					foreach ($subRenderables as $subRenderable) {
213 2
						$newObjects = $this->extractObjects($object[$subRenderable], $newObjectIds);
214
						// and work out what their relationship is to their parent
215 2
						if (!empty($renderables[$subRenderable]['relations'])) {
216 2
							$relations = $renderables[$subRenderable]['relations'];
217 2
							foreach ($relations as $relation) {
218 2
								$memberRef = $relation['memberRef'];
219 2
								if (!isset($extracted['__relations'][$objId][$memberRef]))
220 2
									$extracted['__relations'][$objId][$memberRef] = [];
221 2
								$extracted['__relations'][$objId][$memberRef] = array_merge($extracted['__relations'][$objId][$memberRef], $newObjectIds);
222
							}
223
						}
224 2
						unset($object[$subRenderable]);
225 2
						$extracted = array_merge_recursive(
226 2
							$extracted,
227
							$newObjects
228
						);
229
230
					}
231 2
					$objectClassType = $object['_classType'];
232 2
					unset($object['_renderable'], $object['_classType']);
233 2
					$isNew = in_array($object['_uuid'], $this->newObjects) ? 'new' : 'existing';
234 2
					$extracted[$objectClassType][$isNew][] = $object;
235 2
					$extractedObjectIds[] = $objId;
236
				} else {
237 2
					unset($objects[$key]);
238
				}
239
			}
240
			// and if not go down the next depth
241
			else if (is_array($object)) {
242
				$extracted = array_merge_recursive($extracted, $this->extractObjects($object));
243
			}
244
		}
245 2
		return $extracted;
246
	}
247
248
	/**
249
	 * Check what fields are allowed to be set in a class from the
250
	 * current definition. Objects can get out of step with form definitions
251
	 * so this helps to keep them up to date.
252
	 *
253
	 * @param string $classType  the type of class you want to check again
254
	 * @return array
255
	 */
256 2
	protected function getAllowedFieldsForClass($classType)
257
	{
258 2
		static $fieldsForClass = [];
259 2
		if (empty($fieldsForClass[$classType])) {
260 2
			$definition = $this->getDefinition();
261 2
			foreach ($definition['renderables'] as $r) {
262 2
				if ($r['type'] == 'MemberComponent')
263 2
					$fieldsForClass[$r['class_type']][$r['member_ref']] = null;
264
			}
265
		}
266 2
		return $fieldsForClass[$classType];
267
	}
268
269
270
	/**
271
	 * Save objects to any link members that they need to be saved to
272
	 */
273 2
	private function saveLinkedObjects($relations)
274
	{
275 2
		$dds = $this->getDds();
276 2
		foreach ($relations as $parentUuid => $members) {
277 2
			$parent = $dds->getObject($parentUuid);
278 2
			if ($parent) {
279 2
				$changes = [];
280 2
				foreach ($members as $memberRef => $objectIds) {
281 2
					$changes[$memberRef] = array_unique(array_merge($parent[$memberRef], $objectIds));
282
				}
283 2
				$dds->editObject($parentUuid, $changes);
284
			}
285
		}
286 2
	}
287
288
	/**
289
	 * Convert a set of Daedalus objects into the correct structure for the
290
	 * form to import them.
291
	 *
292
	 * @param type $ddsObjects
293
	 */
294 2
	private function createRenderablesFromDdsData($renderableKey, $ddsObjects)
295
	{
296 2
		$data = [];
297 2
		$renderables = $this->getRenderables();
298 2
		$renderable = $renderables[$renderableKey];
299 2
		if (isset($renderable['items'])) {
300 2
			foreach ($renderable['items'] as $k) {
301 2
				if (isset($ddsObjects[$k])) {
302 2
					$data[$k] = array_merge($ddsObjects[$k], $this->createRenderablesFromDdsData($k, $ddsObjects));
303
				}
304
			}
305
		}
306 2
		return $data;
307
	}
308
309
	/**
310
	 * Link the data objects from Daedalus into the object data
311
	 * - the actual data is pulled in later, but here we create a blank
312
	 * version of the data
313
	 *
314
	 * @param array $dataObjects  set of phoebeKey=>ddsUuid's
315
	 * @return array  created blank objects as renderableKey=>array
316
	 */
317 2
	private function createDdsObjectStubs($dataObjects) {
318 2
		$definition = $this->getDefinition();
319 2
		$renderables = $definition['renderables'];
320 2
		$ddsObjects = [];
321 2
		foreach ($dataObjects as $phoebeKey=>$objectUuid) {
322 2
			if (isset($renderables[$phoebeKey])) {
323
				// check the renderable exists and is a class component
324 2
				$ren = $renderables[$phoebeKey];
325 2
				if ($ren['type'] != 'ClassComponent')
326
					continue;
327
328
				// create the object and any required data params
329
				$object = [
330 2
					"_renderable"=>$phoebeKey,
331 2
					"_classType" => $ren['class_type'],
332 2
					"_uuid" => $objectUuid
333
				];
334 2
				foreach ($ren['items'] as $item) {
335 2
					if (isset($renderables[$item])) {
336 2
						$iRen = $renderables[$item];
337 2
						if ($iRen['type'] != 'MemberComponent')
338 2
							continue;
339 2
						$object[$iRen['member_ref']] = null;
340
					}
341
				}
342
343
				// if it's a repeater put it as subarray
344 2
				if (isset($ren['definition']['isRepeater']) && $ren['definition']['isRepeater']) {
345 2
					$ddsObjects[$phoebeKey][Hash::uuid64()] = $object;
346
				} else {
347 2
					$ddsObjects[$phoebeKey] = $object;
348
				}
349
			}
350
		}
351 2
		return $ddsObjects;
352
	}
353
354
	/**
355
	 * Determine if an object should be saved. This is if it is new and has data or is a
356
	 * preexisting object regardless of data.
357
	 * @param array $object
358
	 * @return boolean
359
	 */
360 2
	private function objectCanBeSaved($object)
361
	{
362 2
		$uuid = !empty($object['_uuid']) ? $object['_uuid'] : null;
363
		//dp('The UUID is', $uuid);
364 2
		unset($object['_uuid'], $object['_classType'], $object['_renderable']);
365 2
		foreach ($object as $k => $v) {
366 2
			if (!empty($v)) {
367
				//dp('The object has got some data so should be saved', $v);
368 2
				return true;
369
			}
370
		}
371
		if (!$uuid) {
372
			//dp('the object had no uuid and no data so nothing to do');
373
			return false;
374
		}
375
		//dp($uuid, 'The object has no data (aah) but does have a uuid. Does it exist?');
376
		// in this case it has a uuid but does the object exist in the database
377
		$data = neon('dds')->IDdsObjectManagement->getObject($uuid);
0 ignored issues
show
Bug Best Practice introduced by
The property IDdsObjectManagement does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug introduced by
The method getObject() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

377
		/** @scrutinizer ignore-call */ 
378
  $data = neon('dds')->IDdsObjectManagement->getObject($uuid);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
378
		//if ($data) {
379
		//	dp($uuid, 'the object already exists therefore save over it');
380
		//}
381
		//dp('The object doesnt exist and has no data so dont save');
382
		return $data !== null;
383
384
	}
385
386
	/**
387
	 * Sets any object data that is provided from external data sources.
388
	 * @param array &$object  the object you want to set data on
389
	 */
390 2
	private function setObjectDataFromDataSource(&$object)
391
	{
392 2
		$objRend = $this->getRenderable($object['_renderable']);
393
		// see if we have any data to be stored
394 2
		if (!empty($objRend['dataSourceMap'])) {
395
			foreach ($objRend['dataSourceMap'] as $member => $dsMap) {
396
				$dsDef = $this->getDataSourceDefinition($dsMap['id']);
397
				if (($dsObj = $this->getDataSourceObject($dsDef['label']))!==null) {
398
					$object[$member] = $dsObj[$dsMap['memberRef']];
399
				}
400
			}
401
		}
402 2
	}
403
404
	/**
405
	 * Get a data source object from its key
406
	 * @param string $key  the data source object key
407
	 */
408
	private function getDataSourceObject($key)
409
	{
410
		return isset($this->dataSourceObjects[$key]) ? $this->dataSourceObjects[$key] : null;
411
	}
412
413
	/**
414
	 * Get the data source definition by its id
415
	 * @param string $id
416
	 * @return array|null  the data source definition if found or null otherwise
417
	 */
418
	private function getDataSourceDefinition($id)
419
	{
420
		$definition = $this->getDefinition();
421
		return (!empty($definition['dataSources'][$id]) ? $definition['dataSources'][$id] : null);
422
	}
423
424
	private $_definition = null;
425 2
	private function getDefinition()
426
	{
427 2
		if (!$this->_definition) {
428 2
			$class = $this->getClass();
429 2
			$this->_definition = $class->definition;
430
		}
431 2
		return $this->_definition;
432
	}
433
434
	private function getFormDefinition()
0 ignored issues
show
Unused Code introduced by
The method getFormDefinition() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
435
	{
436
		return $this->getClass()->getClassFormDefinition();
437
	}
438
439
	private $_renderables = null;
440 2
	private function getRenderables()
441
	{
442 2
		if (!$this->_renderables) {
443 2
			$definition = $this->getDefinition();
444 2
			$this->_renderables = $definition['renderables'];
445
		}
446 2
		return $this->_renderables;
447
	}
448
449 2
	private function getRenderable($key)
450
	{
451 2
		$renderables = $this->getRenderables();
452 2
		return $renderables[$key];
453
	}
454
455
	private $_class = null;
456 2
	private function getClass()
457
	{
458 2
		if (!$this->_class)
459 2
			$this->_class = neon('phoebe')->getIPhoebeType($this->phoebeType)->getClass($this->classType);
460 2
		return $this->_class;
461
	}
462
463 2
	private function isVolatile()
464
	{
465 2
		return $this->getClass()->volatile;
466
	}
467
468
469
	private $_dds = null;
470 2
	private function getDds()
471
	{
472 2
		if (!$this->_dds)
473 2
			$this->_dds = neon('dds')->getIDdsObjectManagement();
474 2
		return $this->_dds;
475
	}
476
477
}