DHLECommerceTracker   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 12
dl 0
loc 158
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A setAccessToken() 0 4 2
B track() 0 25 4
B getAccessToken() 0 22 5
A getCredential() 0 4 1
A sendRequest() 0 9 2
B buildShipment() 0 26 5
A applyDestination() 0 5 1
1
<?php
2
/**
3
 * Slince shipment tracker library
4
 * @author Tao <[email protected]>
5
 */
6
namespace Slince\ShipmentTracking\DHLECommerce;
7
8
use Carbon\Carbon;
9
use GuzzleHttp\Client as HttpClient;
10
use GuzzleHttp\Exception\GuzzleException;
11
use GuzzleHttp\Psr7\Request;
12
use Psr\Http\Message\RequestInterface;
13
use Slince\ShipmentTracking\DHLECommerce\Exception\InvalidAccessTokenException;
14
use Slince\ShipmentTracking\Common\Location\Location;
15
use Slince\ShipmentTracking\Foundation\Shipment;
16
use Slince\ShipmentTracking\Foundation\ShipmentEvent;
17
use Slince\ShipmentTracking\Foundation\HttpAwareTracker;
18
use Slince\ShipmentTracking\Foundation\Exception\TrackException;
19
20
class DHLECommerceTracker extends HttpAwareTracker
21
{
22
    /**
23
     * @var string
24
     */
25
    const ACCESS_TOKEN_ENDPOINT = 'https://api.dhlecommerce.dhl.com/rest/v1/OAuth/AccessToken';
26
27
    /**
28
     * @var string
29
     */
30
    const TRACKING_ENDPOINT = 'https://api.dhlecommerce.dhl.com/rest/v2/Tracking';
31
32
    /**
33
     * @var Credential
34
     */
35
    protected $credential;
36
37
    /**
38
     * @var AccessToken
39
     */
40
    protected $accessToken;
41
42
    public function __construct($clientId, $password, HttpClient $httpClient = null)
43
    {
44
        $httpClient && $this->setHttpClient($httpClient);
45
        $this->credential = new Credential($clientId, $password);
46
    }
47
48
    /**
49
     * Sets the access token for the tracker
50
     * @param string|AccessToken $accessToken
51
     */
52
    public function setAccessToken($accessToken)
53
    {
54
        $this->accessToken = $accessToken instanceof AccessToken ? $accessToken : new AccessToken($accessToken);
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function track($trackingNumber)
61
    {
62
        $parameters = [
63
            'trackItemRequest' => [
64
                'token' => $this->getAccessToken()->getToken(),
65
                'messageLanguage' => 'en',
66
                'messageVersion' => '1.1',
67
                'trackingReferenceNumber' => [$trackingNumber]
68
            ]
69
        ];
70
        $request = new Request('POST', static::TRACKING_ENDPOINT);
71
        $json = $this->sendRequest($request, [
72
            'json' => $parameters
73
        ]);
74
        if ($json['trackItemResponse']['responseCode'] != 0 &&
75
            stripos($json['trackItemResponse']['responseText'], 'Invalid access token') !== false
76
        ) {
77
            throw new InvalidAccessTokenException(sprintf('The access token "%s" is invalid, please refresh for the new one', $this->accessToken));
78
        }
79
        if ($json['trackItemResponse']['responseCode'] != 0) {
80
            throw new TrackException(sprintf('Bad response with code "%d"', $json['trackItemResponse']['responseCode']));
81
        }
82
        return static::buildShipment($json);
83
84
    }
85
86
    /**
87
     * Gets the access token
88
     * @param boolean $refresh
89
     * @throws TrackException
90
     * @return AccessToken
91
     */
92
    public function getAccessToken($refresh = false)
93
    {
94
        if ($this->accessToken && !$refresh) {
95
            return $this->accessToken;
96
        }
97
        $request = new Request('GET', static::ACCESS_TOKEN_ENDPOINT);
98
        $json = $this->sendRequest($request, [
99
            'query' => array_merge($this->credential->toArray(), [
100
                'returnFormat' => 'json'
101
            ])
102
        ]);
103
        if (!isset($json['accessTokenResponse']['responseStatus']) || $json['accessTokenResponse']['responseStatus']['code'] != '100000') {
104
            throw new TrackException(sprintf('Get access token error with message details "%s"',
105
                $json['accessTokenResponse']['responseStatus']['messageDetails']
106
            ));
107
        }
108
        return $this->accessToken = new AccessToken(
109
            $json['accessTokenResponse']['token'],
110
            $json['accessTokenResponse']['token_type'],
111
            $json['accessTokenResponse']['expires_in_seconds']
112
        );
113
    }
114
115
    /**
116
     * @return Credential
117
     */
118
    public function getCredential()
119
    {
120
        return $this->credential;
121
    }
122
123
    /**
124
     * @param RequestInterface $request
125
     * @param array $options
126
     * @throws TrackException
127
     * @return array
128
     * @codeCoverageIgnore
129
     */
130
    protected function sendRequest(RequestInterface $request, $options = [])
131
    {
132
        try {
133
            $response = $this->getHttpClient()->send($request, $options);
134
        } catch (GuzzleException $exception) {
135
            throw new TrackException($exception->getMessage());
136
        }
137
        return \GuzzleHttp\json_decode((string)$response->getBody(), true);
138
    }
139
140
    /**
141
     * @param array $json
142
     * @throws TrackException
143
     * @return Shipment
144
     */
145
    protected static function buildShipment($json)
146
    {
147
        $json = reset($json['trackItemResponse']['items']);
148
        if (!$json) {
149
            throw new TrackException(sprintf('Bad response'));
150
        }
151
        $events = array_map(function($item) {
152
            return ShipmentEvent::fromArray([
153
                'location' => new Location($item['address']['countryCode'], $item['address']['state'], $item['address']['city']),
154
                'description' => $item['description'],
155
                'date' => Carbon::parse($item['timestamp']),
156
                'status' => $item['status']
157
            ]);
158
        }, array_reverse($json['events']));
159
        $shipment = new Shipment($events);
160
        $isDelivered = ($lastEvent = end($events)) ? $lastEvent->getStatus() == 71093 : null;
161
162
        $shipment->setIsDelivered($isDelivered);
0 ignored issues
show
Bug introduced by
It seems like $isDelivered defined by ($lastEvent = end($event...tatus() == 71093 : null on line 160 can also be of type null; however, Slince\ShipmentTracking\...pment::setIsDelivered() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
163
164
        empty($json['destination']['countryCode']) || static::applyDestination($shipment, $json['destination']['countryCode']);
165
166
        if ($firstEvent = reset($events)) {
167
            $shipment->setDeliveredAt($firstEvent->getDate());
168
        }
169
        return $shipment;
170
    }
171
172
    protected static function applyDestination(Shipment $shipment, $countryCode)
173
    {
174
        $destination  = new Location($countryCode, null, null);
175
        $shipment->setDestination($destination);
176
    }
177
}