AbstractEndpoint::processPaged()   B
last analyzed

Complexity

Conditions 7
Paths 16

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
c 0
b 0
f 0
nc 16
nop 3
dl 0
loc 32
rs 8.8333
1
<?php
2
namespace CodeCloud\Bundle\ShopifyBundle\Api\Endpoint;
3
4
use CodeCloud\Bundle\ShopifyBundle\Api\Request\Exception\FailedRequestException;
5
use CodeCloud\Bundle\ShopifyBundle\Api\GenericResource;
6
use GuzzleHttp\ClientInterface;
7
use Psr\Http\Message\RequestInterface;
8
use CodeCloud\Bundle\ShopifyBundle\Api\Response\ErrorResponse;
9
use CodeCloud\Bundle\ShopifyBundle\Api\Response\HtmlResponse;
10
use CodeCloud\Bundle\ShopifyBundle\Api\Response\JsonResponse;
11
use GuzzleHttp\Exception\ClientException;
12
use GuzzleHttp\Psr7\Uri;
13
14
abstract class AbstractEndpoint
15
{
16
    /**
17
     * @var ClientInterface
18
     */
19
    private $client;
20
21
    /**
22
     * @var string
23
     */
24
    private $apiVersion;
25
26
    /**
27
     * @param ClientInterface $client
28
     * @param string $apiVersion
29
     */
30
    public function __construct(ClientInterface $client, $apiVersion = null)
31
    {
32
        $this->client = $client;
33
        $this->apiVersion = $apiVersion;
34
    }
35
36
    /**
37
     * @param RequestInterface $request
38
     * @return \CodeCloud\Bundle\ShopifyBundle\Api\Response\ResponseInterface
39
     * @throws FailedRequestException
40
     */
41
    protected function send(RequestInterface $request)
42
    {
43
        $request = $this->applyApiVersion($request);
44
        $response = $this->process($request);
45
46
        if (! $response->successful()) {
47
            throw new FailedRequestException('Failed request. ' . $response->getHttpResponse()->getReasonPhrase());
48
        }
49
50
        return $response;
51
    }
52
53
    /**
54
     * @param RequestInterface $request
55
     * @param string $rootElement
56
     * @return array
57
     * @throws FailedRequestException
58
     */
59
    protected function sendPaged(RequestInterface $request, $rootElement)
60
    {
61
        return $this->apiVersion
62
            ? $this->processCursor($request, $rootElement)
63
            : $this->processPaged($request, $rootElement)
64
        ;
65
    }
66
67
    /**
68
     * @param array $items
69
     * @param GenericResource|null $prototype
70
     * @return array
71
     */
72
    protected function createCollection($items, GenericResource $prototype = null)
73
    {
74
        if (! $prototype) {
75
            $prototype = new GenericResource();
76
        }
77
78
        $collection = array();
79
80
        foreach ((array)$items as $item) {
81
            $newItem = clone $prototype;
82
            $newItem->hydrate($item);
83
            $collection[] = $newItem;
84
        }
85
86
        return $collection;
87
    }
88
89
    /**
90
     * @param array $data
91
     * @return GenericResource
92
     */
93
    protected function createEntity($data)
94
    {
95
        if (!is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
96
            throw new \InvalidArgumentException(
97
                sprintf('Expected array, got "%s"', var_export($data, true)
98
            ));
99
        }
100
101
        $entity = new GenericResource();
102
        $entity->hydrate($data);
103
104
        return $entity;
105
    }
106
107
    /**
108
     * @param RequestInterface $request
109
     * @return \CodeCloud\Bundle\ShopifyBundle\Api\Response\ResponseInterface
110
     */
111
    protected function process(RequestInterface $request)
112
    {
113
        $guzzleResponse = $this->client->send($request);
114
115
        try {
116
            switch ($request->getHeaderLine('Content-type')) {
117
                case 'application/json':
118
                    $response = new JsonResponse($guzzleResponse);
119
                    break;
120
                default:
121
                    $response = new HtmlResponse($guzzleResponse);
122
            }
123
        } catch (ClientException $e) {
124
            $response = new ErrorResponse($guzzleResponse, $e);
125
        }
126
127
        return $response;
128
    }
129
130
    private function applyApiVersion(RequestInterface $request)
131
    {
132
        if (!$this->apiVersion) {
133
            return $request;
134
        }
135
136
        $uri = $request->getUri();
137
        $uri = $uri->withPath(str_replace("/admin/", "/admin/api/".$this->apiVersion."/", $uri->getPath()));
138
        return $request->withUri($uri);
139
    }
140
141
    /**
142
     * Loop through a set of API results that are available in pages, returning the full resultset as one array
143
     * @param RequestInterface $request
144
     * @param string $rootElement
145
     * @param array $params
146
     * @return array
147
     */
148
    protected function processPaged(RequestInterface $request, $rootElement, array $params = array())
149
    {
150
        $request = $this->applyApiVersion($request);
151
152
        if (empty($params['page'])) {
153
            $params['page'] = 1;
154
        }
155
156
        if (empty($params['limit'])) {
157
            $params['limit'] = 250;
158
        }
159
160
        $allResults = array();
161
162
        do {
163
            $requestUrl = $request->getUri();
164
            $paramDelim = strstr($requestUrl, '?') ? '&' : '?';
165
166
            $pagedRequest = $request->withUri(new Uri($requestUrl . $paramDelim . http_build_query($params)));
167
168
            $response = $this->process($pagedRequest);
169
170
            $root = $response->get($rootElement);
171
172
            if ($pageResults = empty($root) ? false : $root) {
173
                $allResults = array_merge($allResults, $pageResults);
0 ignored issues
show
Bug introduced by
It seems like $pageResults can also be of type string; however, parameter $arrays of array_merge() 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

173
                $allResults = array_merge($allResults, /** @scrutinizer ignore-type */ $pageResults);
Loading history...
174
            }
175
176
            $params['page']++;
177
        } while ($pageResults);
178
179
        return $allResults;
180
    }
181
182
    /**
183
     * Loop through a set of API results that are available in pages, returning the full resultset as one array
184
     * @param RequestInterface $request
185
     * @param string $rootElement
186
     * @return array
187
     */
188
    protected function processCursor(RequestInterface $request, $rootElement)
189
    {
190
        $request = $this->applyApiVersion($request);
191
192
        $allResults = array();
193
194
        do {
195
            $response = $this->process($request);
196
197
            $root = $response->get($rootElement);
198
199
            if (!empty($root)) {
200
                $allResults = array_merge($allResults, $root);
0 ignored issues
show
Bug introduced by
It seems like $root can also be of type string; however, parameter $arrays of array_merge() 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

200
                $allResults = array_merge($allResults, /** @scrutinizer ignore-type */ $root);
Loading history...
201
            }
202
203
            $linkHeader = $response->getHttpResponse()->getHeaderLine('Link');
204
            if (empty($linkHeader)) {
205
                return $allResults;
206
            }
207
208
            $links = extractLinks($linkHeader);
209
            if (empty($links['next'])) {
210
                return $allResults;
211
            }
212
213
            $request = $request->withUri(new Uri($links['next']));
214
        } while (true);
215
216
        return $allResults;
217
    }
218
}
219
220
// lovingly borrowed from: https://community.shopify.com/c/Shopify-APIs-SDKs/How-to-parse-Link-data-from-header-data-in-PHP/td-p/569537#
221
function extractLinks($linkHeader) {
222
    $cleanArray = [];
223
224
    if (strpos($linkHeader, ',') !== false) {
225
        //Split into two or more elements by comma
226
        $linkHeaderArr = explode(',', $linkHeader);
227
    } else {
228
        //Create array with one element
229
        $linkHeaderArr[] = $linkHeader;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$linkHeaderArr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $linkHeaderArr = array(); before regardless.
Loading history...
230
    }
231
232
    foreach ($linkHeaderArr as $linkHeader) {
0 ignored issues
show
introduced by
$linkHeader is overwriting one of the parameters of this function.
Loading history...
233
        $cleanArray += [
234
            extractRel($linkHeader) => extractLink($linkHeader)
235
        ];
236
    }
237
    return $cleanArray;
238
}
239
240
function extractLink($element) {
241
    if (preg_match('/<(.*?)>/', $element, $match) == 1) {
242
        return $match[1];
243
    }
244
    return null;
245
}
246
247
function extractRel($element) {
248
    if (preg_match('/rel="(.*?)"/', $element, $match) == 1) {
249
        return $match[1];
250
    }
251
    return null;
252
}
253