Fetcher   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 117
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
c 1
b 0
f 0
lcom 1
cbo 4
dl 0
loc 117
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B forISBN() 0 26 4
A parseDate() 0 15 4
A getOrDefault() 0 8 2
A isValidISBN() 0 4 1
A extractBook() 0 20 1
1
<?php
2
3
namespace AntoineAugusti\Books;
4
5
use DateTime;
6
use GuzzleHttp\ClientInterface;
7
use InvalidArgumentException;
8
9
class Fetcher
10
{
11
    /**
12
     * @var ClientInterface
13
     */
14
    private $client;
15
16
    /**
17
     * @param ClientInterface $client
18
     */
19
    public function __construct(ClientInterface $client)
20
    {
21
        $this->client = $client;
22
    }
23
24
    /**
25
     * Retrieve information about a book given its ISBN.
26
     *
27
     * @param string $isbn
28
     *
29
     * @throws InvalidArgumentException When the ISBN has not the expected format
30
     * @throws InvalidResponseException When the client got an unexpected response
31
     *
32
     * @return Book
33
     */
34
    public function forISBN($isbn)
35
    {
36
        if (!$this->isValidISBN($isbn)) {
37
            throw new InvalidArgumentException('ISBN is not valid. Got: '.$isbn);
38
        }
39
40
        // Example: https://www.googleapis.com/books/v1/volumes?q=isbn:9780525953739
41
        $response = $this->client->request('GET', 'volumes', [
42
            'query'       => ['q' => 'isbn:'.$isbn],
43
            'http_errors' => false,
44
        ]);
45
46
        $status = $response->getStatusCode();
47
        if ($status != 200) {
48
            throw new InvalidResponseException('Invalid response. Status: '.$status.'. Body: '.$response->getBody());
49
        }
50
51
        $res = json_decode($response->getBody(), true);
52
53
        $totalItems = intval($res['totalItems']);
54
        if ($totalItems != 1) {
55
            throw new InvalidResponseException('Did not get 1 result. Got: '.$totalItems);
56
        }
57
58
        return $this->extractBook($res);
59
    }
60
61
    private function extractBook($res)
62
    {
63
        $item = $res['items'][0];
64
65
        $publishedDate = $this->getOrDefault($item['volumeInfo'], 'publishedDate', null);
66
        list($publishedDate, $publishedDateFormat) = $this->parseDate($publishedDate);
67
68
        return new Book($item['volumeInfo']['title'],
69
            $this->getOrDefault($item['volumeInfo'], 'subtitle', null),
70
            $this->getOrDefault($item['volumeInfo'], 'authors', null),
71
            $this->getOrDefault($item['volumeInfo'], 'printType', null),
72
            intval($this->getOrDefault($item['volumeInfo'], 'pageCount', null)),
73
            $this->getOrDefault($item['volumeInfo'], 'publisher', null),
74
            $publishedDate,
75
            $publishedDateFormat,
76
            $this->getOrDefault($item['volumeInfo'], 'averageRating', null),
77
            $item['volumeInfo']['imageLinks']['thumbnail'],
78
            $this->getOrDefault($item['volumeInfo'], 'language', null),
79
            $this->getOrDefault($item['volumeInfo'], 'categories', []));
80
    }
81
82
    /**
83
     * Parse the publication date.
84
     *
85
     * @param string $rawDate
86
     *
87
     * @return array The publication in DateTime and the date format
88
     */
89
    private function parseDate($rawDate)
90
    {
91
        foreach (['Y-m-d', 'Y-m', 'Y'] as $dateFormat) {
92
            $publishedDate = DateTime::createFromFormat($dateFormat.'|', $rawDate);
93
            if ($publishedDate !== false) {
94
                break;
95
            }
96
        }
97
98
        if ($publishedDate === false) {
0 ignored issues
show
Bug introduced by
The variable $publishedDate does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
99
            $publishedDate = null;
100
        }
101
102
        return [$publishedDate, $dateFormat];
0 ignored issues
show
Bug introduced by
The variable $dateFormat seems to be defined by a foreach iteration on line 91. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
103
    }
104
105
    private function getOrDefault($array, $key, $default)
106
    {
107
        if (array_key_exists($key, $array)) {
108
            return $array[$key];
109
        }
110
111
        return $default;
112
    }
113
114
    /**
115
     * Check if a given ISBN is valid.
116
     *
117
     * @param string $isbn
118
     *
119
     * @return bool
120
     */
121
    private function isValidISBN($isbn)
122
    {
123
        return preg_match('/[0-9]{10,13}/', $isbn);
124
    }
125
}
126