ExporterService   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 39
eloc 60
c 3
b 0
f 0
dl 0
loc 170
rs 9.28
ccs 76
cts 76
cp 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
A hasWildcard() 0 3 1
A collect() 0 3 1
A export() 0 19 5
A getAttributeValueWithWildcard() 0 9 3
A validateAttributesTypes() 0 6 4
A getAttributeValue() 0 18 5
A exportWildcard() 0 4 1
A exportFrom() 0 3 1
A exportArray() 0 7 2
A __construct() 0 3 1
A exportDirectlyNestedByWrapping() 0 5 1
A exportNestedAttributes() 0 3 1
A exportAttribute() 0 11 3
A attributeIsAFunction() 0 7 2
B getNewTarget() 0 18 7
A exportNestedAttribute() 0 3 1
1
<?php
2
3
namespace MathieuTu\Exporter;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Collection;
7
8
class ExporterService
9
{
10
    protected const WILDCARD = '*';
11
12
    protected $exportable;
13
14 21
    public function __construct($exportable)
15
    {
16 21
        $this->exportable = $exportable;
17 21
    }
18
19
    /**
20
     * @param mixed $exportable
21
     * @param mixed $attributes
22
     * @return Collection|mixed
23
     */
24 7
    public static function exportFrom($exportable, $attributes)
25
    {
26 7
        return (new self($exportable))->export($attributes);
27
    }
28
29
    /**
30
     * @param array|int|string $attributes
31
     * @return Collection|mixed
32
     */
33 21
    public function export($attributes)
34
    {
35 21
        $this->validateAttributesTypes($attributes);
36
37 20
        if (!is_array($attributes) || $this->hasWildcard($attributes)) {
38 4
            return $this->exportDirectlyNestedByWrapping($attributes);
39
        }
40
41 20
        return $this->collect($attributes)
42 20
            ->mapWithKeys(function ($attribute, $key) {
43 20
                if (is_array($attribute)) {
44 5
                    return $this->exportArray($key, $attribute);
45
                }
46
47 20
                if (!is_int($key)) {
48 5
                    return $this->exportNestedAttribute($key, $attribute);
49
                }
50
51 17
                return $this->exportAttribute($attribute);
52 20
            });
53
    }
54
55 21
    private function validateAttributesTypes($attributes): void
56
    {
57 21
        if (!is_array($attributes) && !is_string($attributes) && !is_int($attributes)) {
58 1
            $type = gettype($attributes);
59
60 1
            throw new \InvalidArgumentException("Exporter only accept array, string or int attribute. '{$type}' passed.");
61
        }
62 20
    }
63
64 20
    protected function hasWildcard(array $array): bool
65
    {
66 20
        return array_keys($array) === [self::WILDCARD];
67
    }
68
69 4
    private function exportDirectlyNestedByWrapping($attributes)
70
    {
71 4
        $key = '***ExporterWrapperKey***';
72
73 4
        return self::exportFrom([$key => $this->exportable], [$key => $attributes])[$key];
74
    }
75
76 20
    protected function collect($items): Collection
77
    {
78 20
        return Collection::make($items);
79
    }
80
81 5
    protected function exportArray(string $key, array $attribute): array
82
    {
83 5
        if ($this->hasWildcard($attribute)) {
84 4
            return $this->exportWildcard($key, $attribute);
85
        }
86
87 2
        return $this->exportNestedAttributes($key, $attribute);
88
    }
89
90 4
    protected function exportWildcard(string $key, array $array): array
91
    {
92 4
        return [$key => $this->collect($this->getAttributeValue($key))->map(function ($exportable) use ($array) {
93 4
            return self::exportFrom($exportable, $array['*']);
94 4
        })];
95
    }
96
97 19
    protected function getAttributeValue($attributes)
98
    {
99 19
        $attributes = is_array($attributes) ? $attributes : explode('.', $attributes);
100
101 19
        $target = $this->exportable;
102 19
        while (($segment = array_shift($attributes)) !== null) {
103 19
            if ($segment === self::WILDCARD) {
104 2
                return $this->getAttributeValueWithWildcard($target, $attributes);
105
            }
106
107
            try {
108 19
                $target = $this->getNewTarget($target, $segment);
109 2
            } catch (NotFoundException $e) {
110 2
                return null;
111
            }
112
        }
113
114 17
        return $target;
115
    }
116
117 2
    protected function getAttributeValueWithWildcard($target, $attribute): ?array
118
    {
119 2
        if (!is_iterable($target)) {
120 2
            return null;
121
        }
122
123 2
        $result = Arr::pluck($target, $attribute);
124
125 2
        return in_array(self::WILDCARD, $attribute) ? Arr::collapse($result) : $result;
126
    }
127
128 19
    protected function getNewTarget($target, $segment)
129
    {
130 19
        if (Arr::accessible($target) && Arr::exists($target, $segment)) {
131 14
            return $target[$segment];
132
        }
133
134 7
        if (is_object($target) && isset($target->{$segment})) {
135 6
            return $target->{$segment};
136
        }
137
138 5
        $studlySegment = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $segment)));
139 5
        $getter = "get{$studlySegment}";
140
141 5
        if (is_object($target) && method_exists($target, $getter)) {
142 3
            return call_user_func([$target, $getter]);
143
        }
144
145 2
        throw new NotFoundException($segment, $target);
146
    }
147
148 2
    protected function exportNestedAttributes(string $key, array $array): array
149
    {
150 2
        return [$key => self::exportFrom($this->getAttributeValue($key), $array)];
151
    }
152
153 5
    protected function exportNestedAttribute($key, $attribute): array
154
    {
155 5
        return [$key => $this->getAttributeValue("$key.$attribute")];
156
    }
157
158 17
    protected function exportAttribute(string $attribute): ?array
159
    {
160 17
        if ($export = $this->attributeIsAFunction($attribute)) {
161 2
            return $export;
162
        }
163
164 16
        if (preg_match('/^(.*) as (.*)$/', $attribute, $matches)) {
165 2
            return [$matches[2] => $this->getAttributeValue($matches[1])];
166
        }
167
168 15
        return [$attribute => $this->getAttributeValue($attribute)];
169
    }
170
171 17
    protected function attributeIsAFunction($attribute): ?array
172
    {
173 17
        if (preg_match("/(.*)\((.*)\)$/", $attribute, $matches)) {
174 2
            return [$matches[1] => call_user_func_array([$this->exportable, $matches[1]], array_map('trim', explode(',', $matches[2])))];
175
        }
176
177 16
        return null;
178
    }
179
}
180