Completed
Push — dev ( cfc77a...05eebc )
by Marcin
02:13
created

Converter   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 50
c 4
b 0
f 0
dl 0
loc 123
rs 10
wmc 20

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getClassMappingConfigOrThrow() 0 23 5
A __construct() 0 9 2
B convertArray() 0 37 9
A convert() 0 15 4
1
<?php
2
declare(strict_types=1);
3
4
namespace MarcinOrlowski\ResponseBuilder;
5
6
/**
7
 * Laravel API Response Builder
8
 *
9
 * @package   MarcinOrlowski\ResponseBuilder
10
 *
11
 * @author    Marcin Orlowski <mail (#) marcinorlowski (.) com>
12
 * @copyright 2016-2019 Marcin Orlowski
13
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
14
 * @link      https://github.com/MarcinOrlowski/laravel-api-response-builder
15
 */
16
17
use Illuminate\Support\Facades\Config;
18
19
20
/**
21
 * Data converter
22
 */
23
class Converter
24
{
25
	/**
26
	 * @var array|null
27
	 */
28
	protected $classes;
29
30
	public function __construct()
31
	{
32
		$classes = Config::get(ResponseBuilder::CONF_KEY_CLASSES) ?? [];
33
		if (!is_array($classes)) {
34
			throw new \RuntimeException(
35
				sprintf('CONFIG: "classes" mapping must be an array (%s given)', gettype($classes)));
36
		}
37
38
		$this->classes = $classes;
39
	}
40
41
	/**
42
	 * Checks if we have "classes" mapping configured for $data object class.
43
	 * Returns @true if there's valid config for this class.
44
	 *
45
	 * @param object $data Object to check mapping for.
46
	 *
47
	 * @return array
48
	 *
49
	 * @throws \RuntimeException if there's no config "classes" mapping entry
50
	 *                           for this object configured.
51
	 */
52
	protected function getClassMappingConfigOrThrow(object $data): array
53
	{
54
		$result = null;
55
56
		// check for exact class name match...
57
		$cls = get_class($data);
58
		if (array_key_exists($cls, $this->classes)) {
0 ignored issues
show
Bug introduced by
It seems like $this->classes can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

58
		if (array_key_exists($cls, /** @scrutinizer ignore-type */ $this->classes)) {
Loading history...
59
			$result = $this->classes[ $cls ];
60
		} else {
61
			// no exact match, then lets try with `instanceof`
62
			foreach ($this->classes as $class_name => $params) {
63
				if ($data instanceof $class_name) {
64
					$result = $this->classes[ $class_name ];
65
					break;
66
				}
67
			}
68
		}
69
70
		if ($result === null) {
71
			throw new \InvalidArgumentException(sprintf('No data conversion mapping configured for "%s" class.', $cls));
72
		}
73
74
		return $result;
75
	}
76
77
	/**
78
	 * We need to prepare source data
79
	 *
80
	 * @param null $data
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $data is correct as it would always require null to be passed?
Loading history...
81
	 *
82
	 * @return array|null
83
	 */
84
	public function convert($data = null): ?array
85
	{
86
		if ($data === null) {
0 ignored issues
show
introduced by
The condition $data === null is always true.
Loading history...
87
			return null;
88
		}
89
90
		if (is_object($data)) {
91
			$cfg = $this->getClassMappingConfigOrThrow($data);
92
			$data = [$cfg[ ResponseBuilder::KEY_KEY ] => $data->{$cfg[ ResponseBuilder::KEY_METHOD ]}()];
93
		} elseif (!is_array($data)) {
94
			throw new \InvalidArgumentException(
95
				sprintf('Invalid payload data. Must be null, array or object with mapping ("%s" given).', gettype($data)));
96
		}
97
98
		return $this->convertArray($data);
99
	}
100
101
	/**
102
	 * Recursively walks $data array and converts all known objects if found. Note
103
	 * $data array is passed by reference so source $data array may be modified.
104
	 *
105
	 * @param array $data array to recursively convert known elements of
106
	 *
107
	 * @return array
108
	 */
109
	protected function convertArray(array $data): array
110
	{
111
		// This is to ensure that we either have array with user provided keys i.e. ['foo'=>'bar'], which will then
112
		// be turned into JSON object or array without user specified keys (['bar']) which we would return as JSON
113
		// array. But you can't mix these two as the final JSON would not produce predictable results.
114
		$string_keys_cnt = 0;
115
		$int_keys_cnt = 0;
116
		foreach ($data as $key => $val) {
117
			if (is_int($key)) {
118
				$int_keys_cnt++;
119
			} else {
120
				$string_keys_cnt++;
121
			}
122
123
			if (($string_keys_cnt > 0) && ($int_keys_cnt > 0)) {
124
				throw new \RuntimeException(
125
					'Invalid data array. Either set own keys for all the items or do not specify any keys at all. ' .
126
					'Arrays with mixed keys are not supported by design.');
127
			}
128
		}
129
130
		foreach ($data as $key => $val) {
131
			if (is_array($val)) {
132
				$data[ $key ] = $this->convertArray($val);
133
			} elseif (is_object($val)) {
134
				$cls = get_class($val);
135
				$cfg = $this->getClassMappingConfigOrThrow($val);
0 ignored issues
show
Unused Code introduced by
The assignment to $cfg is dead and can be removed.
Loading history...
136
				if (array_key_exists($cls, $this->classes)) {
0 ignored issues
show
Bug introduced by
It seems like $this->classes can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

136
				if (array_key_exists($cls, /** @scrutinizer ignore-type */ $this->classes)) {
Loading history...
137
					$conversion_method = $this->classes[ $cls ][ ResponseBuilder::KEY_METHOD ];
138
					$converted_data = $val->$conversion_method();
139
//							$data = [$this->classes[ $cls ][ ResponseBuilder::KEY_KEY ] => $converted_data];
140
					$data[ $key ] = $converted_data;
141
				}
142
			}
143
		}
144
145
		return $data;
146
	}
147
}
148