ApiFormatterComponent   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 49
c 1
b 0
f 0
dl 0
loc 149
rs 10
wmc 18

6 Methods

Rating   Name   Duplication   Size   Complexity  
A embedIncluded() 0 27 5
A extractFromIncluded() 0 18 3
A cleanResponse() 0 5 1
A replaceWithTranslation() 0 22 4
A addIncluded() 0 12 3
A extractTranslatedFields() 0 10 2
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2021 Atlas Srl, ChannelWeb Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\WebTools\Controller\Component;
17
18
use BEdita\WebTools\Utility\ApiTools;
19
use Cake\Collection\Collection;
20
use Cake\Controller\Component;
21
use Cake\Utility\Hash;
22
23
/**
24
 * Component class to format API response data.
25
 */
26
class ApiFormatterComponent extends Component
27
{
28
    /**
29
     * Embed included data into relationships.
30
     *
31
     * @param array $response The response from API
32
     * @return array
33
     */
34
    public function embedIncluded(array $response): array
35
    {
36
        $data = (array)Hash::get($response, 'data');
37
        if (empty($data)) {
38
            return $response;
39
        }
40
41
        $included = (array)Hash::get($response, 'included');
42
        if (empty($included)) {
43
            return $response;
44
        }
45
46
        $included = collection($included);
47
        if (!Hash::numeric(array_keys($data))) {
48
            $response['data'] = $this->addIncluded($data, $included);
49
50
            return $response;
51
        }
52
53
        foreach ($data as &$d) {
54
            $d = $this->addIncluded($d, $included);
55
        }
56
        unset($d);
57
58
        $response['data'] = $data;
59
60
        return $response;
61
    }
62
63
    /**
64
     * Add included data to main resource.
65
     *
66
     * @param array $resource The resource.
67
     * @param \Cake\Collection\Collection $included The included collection.
68
     * @return array
69
     */
70
    protected function addIncluded(array $resource, Collection $included): array
71
    {
72
        foreach ($resource['relationships'] as &$relation) {
73
            if (empty($relation['data'])) {
74
                continue;
75
            }
76
77
            $relation['data'] = $this->extractFromIncluded($included, (array)$relation['data']);
78
        }
79
        unset($relation);
80
81
        return $resource;
82
    }
83
84
    /**
85
     * Extract items from included starting from $relationship data.
86
     *
87
     * @param \Cake\Collection\Collection $included The included collection
88
     * @param array $relationshipData Array of relationship data.
89
     *                                Every item must contain 'type' and 'id'.
90
     * @return array
91
     */
92
    protected function extractFromIncluded(Collection $included, array $relationshipData): array
93
    {
94
        // case is 1-1 relationship - object relation in translations is a special case
95
        if (array_key_exists('id', $relationshipData)) {
96
            return (array)$included->firstMatch([
97
                'type' => $relationshipData['type'],
98
                'id' => $relationshipData['id'],
99
            ]);
100
        }
101
        foreach ($relationshipData as &$data) {
102
            $data = (array)$included->firstMatch([
103
                'type' => $data['type'],
104
                'id' => $data['id'],
105
            ]);
106
        }
107
        unset($data);
108
109
        return $relationshipData;
110
    }
111
112
    /**
113
     * Replace a translation in main objects.
114
     * It must be used after `included` data have been embedded using `self::embedIncluded()`.
115
     *
116
     * @param array $response The response API array
117
     * @param string $lang The lang to search in translations
118
     * @return array
119
     */
120
    public function replaceWithTranslation(array $response, string $lang): array
121
    {
122
        if (empty($response['data'])) {
123
            return $response;
124
        }
125
126
        $data = (array)Hash::get($response, 'data');
127
128
        if (!Hash::numeric(array_keys($data))) {
129
            $response['data']['attributes'] = array_merge(
130
                $response['data']['attributes'],
131
                $this->extractTranslatedFields($data, $lang)
132
            );
133
134
            return $response;
135
        }
136
137
        foreach ($response['data'] as &$d) {
138
            $d['attributes'] = array_merge($d['attributes'], $this->extractTranslatedFields($d, $lang));
139
        }
140
141
        return $response;
142
    }
143
144
    /**
145
     * Extract translated fields for a language.
146
     *
147
     * @param array $data The object data
148
     * @param string $lang The lang to extract
149
     * @return array
150
     */
151
    protected function extractTranslatedFields(array $data, string $lang): array
152
    {
153
        $path = sprintf('relationships.translations.data.{n}.attributes[lang=%s].translated_fields', $lang);
154
        if ($lang === Hash::get($data, 'attributes.lang')) {
155
            return [];
156
        }
157
158
        $translatedFields = (array)Hash::extract($data, $path);
159
160
        return array_filter((array)array_shift($translatedFields));
161
    }
162
163
    /**
164
     * Clean response from unwanted keys (recursively).
165
     *
166
     * @param array $response The response from API
167
     * @param array $options The options to clean response
168
     * @return array
169
     */
170
    public function cleanResponse(
171
        array $response,
172
        array $options = ['included', 'links', 'schema', 'relationships', 'attributes' => []]
173
    ): array {
174
        return ApiTools::cleanResponse($response, $options);
175
    }
176
}
177