Completed
Branch dev (fcb395)
by Marcin
06:22 queued 03:31
created

Converter::getClasses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
	public function getClasses(): array
42
	{
43
		return $this->classes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->classes could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
44
	}
45
46
	/**
47
	 * Checks if we have "classes" mapping configured for $data object class.
48
	 * Returns @true if there's valid config for this class.
49
	 *
50
	 * @param object $data Object to check mapping for.
51
	 *
52
	 * @return array
53
	 *
54
	 * @throws \RuntimeException if there's no config "classes" mapping entry
55
	 *                           for this object configured.
56
	 */
57
	protected function getClassMappingConfigOrThrow(object $data): array
58
	{
59
		$result = null;
60
61
		// check for exact class name match...
62
		$cls = get_class($data);
63
		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

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

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