Completed
Push — dev ( 2e63e0...7cd82e )
by Marcin
02:59 queued 10s
created

Converter::hasClassesMapping()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 3
b 0
f 0
nc 4
nop 1
dl 0
loc 18
rs 9.9666
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 ($cls instanceof $class_name) {
64
					$result = $this->classes[ $cls ];
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
	/** We need to prepare t */
78
	public function convert($data = null): ?array
79
	{
80
		if (is_object($data)) {
81
			$cfg = $this->getClassMappingConfigOrThrow($data);
82
			$data = [$cfg[ ResponseBuilder::KEY_KEY ] => $data->{$cfg[ ResponseBuilder::KEY_METHOD ]}()];
83
		} else {
84
			if (!is_array($data) && $data !== null) {
85
				throw new \InvalidArgumentException(
86
					sprintf('Invalid payload data. Must be null, array or class with mapping ("%s" given).', gettype($data)));
87
			}
88
		}
89
90
		return $this->convertArray($data);
91
	}
92
93
	/**
94
	 * Recursively walks $data array and converts all known objects if found. Note
95
	 * $data array is passed by reference so source $data array may be modified.
96
	 *
97
	 * @param array|null $data array to recursively convert known elements of
98
	 *
99
	 * @return array|null
100
	 */
101
	protected function convertArray(array $data = null): ?array
102
	{
103
		if ($data === null) {
104
			return null;
105
		}
106
107
		if (!is_array($data) && !is_object($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
108
			throw new \InvalidArgumentException(
109
				sprintf('Invalid payload data. Must be null, array or class with mapping ("%s" given).', gettype($data)));
110
		}
111
112
		if (is_object($data)) {
0 ignored issues
show
introduced by
The condition is_object($data) is always false.
Loading history...
113
			$cfg = $this->getClassMappingConfigOrThrow($data);
114
115
			return [$cfg[ ResponseBuilder::KEY_KEY ] => $data->{$cfg[ ResponseBuilder::KEY_METHOD ]}()];
116
		}
117
118
		// This is to ensure that we either have array with user provided keys i.e. ['foo'=>'bar'], which will then
119
		// be turned into JSON object or array without user specified keys (['bar']) which we would return as JSON
120
		// array. But you can't mix these two as the final JSON would not produce predictable results.
121
		$user_keys_cnt = 0;
122
		$builtin_keys_cnt = 0;
123
		foreach ($data as $key => $val) {
124
			if (is_int($key)) {
125
				$builtin_keys_cnt++;
126
			} elseif (is_string($key)) {
127
				$user_keys_cnt++;
128
			} else {
129
				throw new \RuntimeException('Invalid data array. Array keys must either use strings as keys, or not use user provide keys.');
130
			}
131
132
			if (($user_keys_cnt > 0) && ($builtin_keys_cnt > 0)) {
133
				throw new \RuntimeException(
134
					'Invalid data array. Either set own keys for all the items or do not specify any keys at all. ' .
135
					'Arrays with mixed keys are not supported by design.');
136
			}
137
		}
138
139
		foreach ($data as $key => $val) {
140
			if (is_array($val)) {
141
				foreach ($val as $val_key => $val_val) {
142
//					if (is_object($val_val) && (!is_string($val_key))) {
143
//						throw new \InvalidArgumentException(
144
//							sprintf('Invalid payload data. Must be null, array or object ("%s" given).', gettype($data)));
145
//					}
146
				}
147
				$data[ $key ] = $this->convertArray($val);
148
			} elseif (is_object($val)) {
149
				$cls = get_class($val);
150
				$cfg = $this->getClassMappingConfigOrThrow($val);
0 ignored issues
show
Unused Code introduced by
The assignment to $cfg is dead and can be removed.
Loading history...
151
				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

151
				if (array_key_exists($cls, /** @scrutinizer ignore-type */ $this->classes)) {
Loading history...
152
					$conversion_method = $this->classes[ $cls ][ ResponseBuilder::KEY_METHOD ];
153
					$converted_data = $val->$conversion_method();
154
//							$data = [$this->classes[ $cls ][ ResponseBuilder::KEY_KEY ] => $converted_data];
155
					$data[ $key ] = $converted_data;
156
				}
157
			}
158
		}
159
160
		return $data;
161
	}
162
}
163