• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gam6itko / ozon-seller / 18650037214

20 Oct 2025 11:05AM UTC coverage: 75.794% (+8.3%) from 67.529%
18650037214

push

github

web-flow
Add integer check in ProductValidator (#107)

* Add integer check in ProductValidator

8 of 8 new or added lines in 2 files covered. (100.0%)

3 existing lines in 1 file now uncovered.

1002 of 1322 relevant lines covered (75.79%)

38.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.15
/src/Service/AbstractService.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Gam6itko\OzonSeller\Service;
6

7
use Gam6itko\OzonSeller\Exception\OzonSellerException;
8
use Gam6itko\OzonSeller\Utils\ArrayHelper;
9
use Psr\Http\Client\ClientInterface;
10
use Psr\Http\Client\RequestExceptionInterface;
11
use Psr\Http\Client\NetworkExceptionInterface;
12
use Psr\Http\Message\RequestFactoryInterface;
13
use Psr\Http\Message\RequestInterface;
14
use Psr\Http\Message\StreamFactoryInterface;
15

16
/**
17
 * @author Alexander Strizhak <gam6itko@gmail.com>
18
 *
19
 * @psalm-type TOzonErrorData = array{
20
 *     code?: numeric,
21
 *     error?: array{code?: string},
22
 *     message?: string,
23
 *     details?: array,
24
 *     data?: array
25
 * }
26
 */
27
abstract class AbstractService
28
{
29
    /** @var array */
30
    private $config;
31

32
    /** @var ClientInterface */
33
    protected $client;
34

35
    /** @var RequestFactoryInterface */
36
    protected $requestFactory;
37

38
    /** @var StreamFactoryInterface */
39
    protected $streamFactory;
40

41
    public function __construct(array $config, ClientInterface $client, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null)
42
    {
43
        $this->parseConfig($config);
775✔
44
        $this->client = $client;
759✔
45

46
        // request factory
47
        if (!$requestFactory && $this->client instanceof RequestFactoryInterface) {
759✔
48
            $requestFactory = $this->client;
7✔
49
        }
50
        assert(null !== $requestFactory);
759✔
51
        $this->requestFactory = $requestFactory;
759✔
52

53
        // stream factory
54
        if (!$streamFactory && $this->client instanceof StreamFactoryInterface) {
759✔
55
            $streamFactory = $this->client;
7✔
56
        }
57
        assert(null !== $streamFactory);
759✔
58
        $this->streamFactory = $streamFactory;
759✔
59
    }
284✔
60

61
    protected function getDefaultHost(): string
62
    {
63
        return 'https://api-seller.ozon.ru';
111✔
64
    }
65

66
    private function parseConfig(array $config): void
67
    {
68
        $keys = ['clientId', 'apiKey', 'host'];
775✔
69

70
        if (!$this->isAssoc($config)) {
775✔
71
            if (count($config) > 3) {
751✔
72
                throw new \LogicException('To many config parameters');
8✔
73
            }
74
            $config = array_combine($keys, array_pad($config, 3, null));
743✔
75
        }
76

77
        if (empty($config['clientId']) || empty($config['apiKey'])) {
767✔
78
            throw new \LogicException('Not defined mandatory config parameters `clientId` or `apiKey`');
8✔
79
        }
80

81
        $config['host'] = rtrim($config['host'] ?? $this->getDefaultHost(), '/');
759✔
82

83
        $this->config = ArrayHelper::pick($config, $keys);
759✔
84
    }
284✔
85

86
    protected function createRequest(string $method, string $uri = '', $body = null): RequestInterface
87
    {
88
        if (is_array($body)) {
647✔
89
            $body = json_encode($body);
615✔
90
            if (JSON_ERROR_NONE !== json_last_error()) {
615✔
UNCOV
91
                throw new \RuntimeException('json_encode error: '.json_last_error_msg());
×
92
            }
93
        }
94

95
        $request = $this->requestFactory
647✔
96
            ->createRequest($method, $this->config['host'].$uri)
647✔
97
            ->withHeader('Client-Id', $this->config['clientId'])
647✔
98
            ->withHeader('Api-Key', $this->config['apiKey'])
647✔
99
            ->withHeader('Content-Type', 'application/json');
647✔
100

101
        if ($body) {
647✔
102
            $request = $request->withBody($this->streamFactory->createStream($body));
639✔
103
        }
104

105
        return $request;
647✔
106
    }
107

108
    /**
109
     * @param array|string|null $body
110
     */
111
    protected function request(string $method, string $uri = '', $body = null, bool $parseResultAsJson = true, bool $returnOnlyResult = true)
112
    {
113
        try {
114
            $request = $this->createRequest($method, $uri, $body);
647✔
115
            $response = $this->client->sendRequest($request);
647✔
116
            $responseBody = $response->getBody();
615✔
117

118
            // nyholm/psr7
119
            if ($response->getStatusCode() >= 400) {
615✔
120
                $this->throwOzonException($responseBody->getContents() ?: "Error status code: {$response->getStatusCode()}");
40✔
121
            }
122

123
            if (!$parseResultAsJson) {
575✔
124
                return $responseBody->getContents();
16✔
125
            }
126

127
            if ($responseBody->isSeekable() && 0 !== $responseBody->tell()) {
559✔
UNCOV
128
                $responseBody->rewind();
×
129
            }
130

131
            $arr = json_decode($responseBody->getContents(), true);
559✔
132
            if (JSON_ERROR_NONE !== json_last_error()) {
559✔
UNCOV
133
                throw new \RuntimeException('Invalid json response: '.$arr);
×
134
            }
135

136
            if (isset($arr['result']) && $returnOnlyResult) {
559✔
137
                return $arr['result'];
551✔
138
            }
139

140
            return $arr;
8✔
141
        } catch (RequestExceptionInterface | NetworkExceptionInterface $requestException) {
72✔
142
            // guzzle
143
            if (method_exists($requestException, 'getResponse')) {
32✔
144
                $response = $requestException->getResponse();
24✔
145
                if (method_exists($response, 'getBody')) {
24✔
146
                    $contents = $response->getBody()->getContents();
24✔
147
                    $this->throwOzonException($contents ?: "Error status code: {$response->getStatusCode()}");
24✔
148
                }
149
            }
150
            throw $requestException;
8✔
151
        }
152
    }
153

154
    protected function throwOzonException(string $responseBodyContents): void
155
    {
156
        /** @var TOzonErrorData $errorData */
157
        $errorData = json_decode($responseBodyContents, true);
64✔
158
        if (JSON_ERROR_NONE !== json_last_error()) {
64✔
159
            throw new OzonSellerException($responseBodyContents);
8✔
160
        }
161

162
        if (!isset($errorData['error']) || empty($errorData['error']['code'])) {
56✔
163
            throw new OzonSellerException($errorData['message'] ?? 'Ozon error', (int) ($errorData['code'] ?? 0), $errorData['details'] ?? []);
16✔
164
        }
165

166
        if (!class_exists($className = $this->getExceptionClassByName($errorData['error']['code']))) {
40✔
167
            throw new OzonSellerException($responseBodyContents);
8✔
168
        }
169

170
        $errorData = array_merge([
32✔
171
            'message' => '',
32✔
172
            'data'    => [],
20✔
173
        ], $errorData['error']);
32✔
174

175
        $refClass = new \ReflectionClass($className);
32✔
176
        /** @var \Throwable $instance */
177
        $instance = $refClass->newInstance($errorData['message'], 0, $errorData['data'] ?? []);
32✔
178
        throw $instance;
32✔
179
    }
180

181
    private function getExceptionClassByName(string $code): string
182
    {
183
        $parts = array_filter(explode('_', strtolower($code)));
40✔
184
        // 'error' будет заменен на Exception
185
        if ('error' === end($parts)) {
40✔
186
            unset($parts[key($parts)]);
8✔
187
        }
188
        $parts = array_map('ucfirst', $parts);
40✔
189
        $name = implode('', $parts);
40✔
190

191
        return "Gam6itko\\OzonSeller\\Exception\\{$name}Exception";
40✔
192
    }
193

194
    protected function ensureCollection(array $arr)
195
    {
196
        return $this->isAssoc($arr) ? [$arr] : $arr;
64✔
197
    }
198

199
    protected function isAssoc(array $arr): bool
200
    {
201
        return array_keys($arr) !== range(0, count($arr) - 1);
775✔
202
    }
203
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc