Completed
Push — master ( 47778c...ba169d )
by Dan Michael O.
10:36
created

Report::__get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace Scriptotek\Alma\Analytics;
4
5
use Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement;
6
use Scriptotek\Alma\Client;
7
use Scriptotek\Alma\Exception\RequestFailed;
8
use Scriptotek\Alma\Model\PagedLazyResourceList;
9
10
/**
11
 * A single Report resource.
12
 */
13
class Report extends PagedLazyResourceList implements \Iterator, \Countable
14
{
15
    /** @var Client */
16
    protected $client;
17
18
    /** @var string */
19
    public $path;
20
21
    /** @var string */
22
    public $filter;
23
24
    /** @var array */
25
    protected $headers = [];
26
27
    /** @var string */
28
    protected $resumptionToken = null;
29
30
    /** @var boolean */
31
    protected $isFinished = false;
32
33
    /** @var integer */
34
    public $chunkSize = 1000;
35
36
    public static $maxAttempts = 5;
37
38
    public static $retryDelayTime = 3;
39
40
    public function __construct(Client $client = null, $path = null, $headers = [], $filter = null)
41
    {
42
        parent::__construct($client);
0 ignored issues
show
Bug introduced by
It seems like $client defined by parameter $client on line 40 can be null; however, Scriptotek\Alma\Model\LazyResource::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
43
44
        $this->path = $path;
45
        $this->headers = $headers;
46
        $this->filter = $filter;
47
    }
48
49
    /**
50
     * @deprecated
51
     * @return $this
52
     */
53
    public function getRows()
54
    {
55
        return $this;
56
    }
57
58
59
    public function getHeaders()
60
    {
61
        if (!count($this->headers)) {
62
            $this->fetchBatch();
63
        }
64
65
        return $this->headers;
66
    }
67
68
    /**
69
     * Generate the base URL for this resource.
70
     *
71
     * @return string
72
     */
73
    protected function urlBase()
74
    {
75
        return '/analytics/reports';
76
    }
77
78
    /**
79
     * Convert a retrieved resource to an object.
80
     *
81
     * @param $data
82
     * @return mixed
83
     */
84
    protected function convertToResource($data)
85
    {
86
        return new Row($data, $this->headers);
87
    }
88
89
    /**
90
     * Note: chunkSize must be between 25 and 1000.
91
     *
92
     * @param int $attempt
93
     * @param int $chunkSize
94
     * @return void
95
     */
96
    protected function fetchBatch($attempt = 1, $chunkSize = null)
97
    {
98
        if ($this->isFinished) {
99
            return;
100
        }
101
102
        $results = $this->client->getXML($this->url('', [
103
            'path'   => $this->resumptionToken ? null : $this->path,
104
            'limit'  => $chunkSize ?: $this->chunkSize,
105
            'token'  => $this->resumptionToken,
106
            'filter' => $this->filter ? str_replace(['\''], ['&apos;'], $this->filter) : null,
107
        ]));
108
109
        $results->registerXPathNamespaces([
110
            'rowset'  => 'urn:schemas-microsoft-com:xml-analysis:rowset',
111
            'xsd'     => 'http://www.w3.org/2001/XMLSchema',
112
            'saw-sql' => 'urn:saw-sql',
113
        ]);
114
115
        $this->readColumnHeaders($results);
116
117
        $rows = $results->all('//rowset:Row');
118
119
        foreach ($rows as $row) {
120
            $this->resources[] = $this->convertToResource($row);
121
        }
122
123
        $this->resumptionToken = $results->text('/report/QueryResult/ResumptionToken') ?: $this->resumptionToken;
124
        $this->isFinished = ($results->text('/report/QueryResult/IsFinished') === 'true');
125
126
127
        if (!count($rows) && !$this->isFinished) {
128
            // If the Analytics server spends too long time preparing the results, it can
129
            // sometimes return an empty result set. If this happens, we should just wait
130
            // a little and retry the request.
131
            // See: https://bitbucket.org/uwlib/uwlib-alma-analytic-tools/wiki/Understanding_Analytic_GET_Requests#!analytic-still-loading
132
            if ($attempt >= self::$maxAttempts) {
133
                // Give up
134
                throw new RequestFailed(
135
                    'Not getting any data from the Analytics server - max number of retries exhausted.'
136
                );
137
            }
138
            // Sleep for a few seconds, then retry
139
            sleep(self::$retryDelayTime);
140
            $this->fetchBatch($attempt + 1);
141
        }
142
    }
143
144
    /**
145
     * Read column headers from response, and check that we got the right number of columns back.
146
     *
147
     * @param QuiteSimpleXMLElement $results
148
     */
149
    protected function readColumnHeaders(QuiteSimpleXMLElement $results)
150
    {
151
        $headers = array_map(function (QuiteSimpleXMLElement $node) {
152
            return $node->attr('saw-sql:columnHeading');
153
        }, $results->all('//xsd:complexType[@name="Row"]/xsd:sequence/xsd:element[position()>1]'));
154
155
        if (!count($headers)) {
156
            // No column headers included in this response. They're only
157
            // included in the first response, so that's probably fine.
158
            return;
159
        }
160
161
        if (!count($this->headers)) {
162
            $this->headers = $headers;
163
164
            return;
165
        }
166
167
        if (count($headers) != count($this->headers)) {
168
            throw new \RuntimeException(sprintf(
169
                'The number of returned columns (%d) does not match the number of assigned headers (%d).',
170
                count($headers),
171
                count($this->headers)
172
            ));
173
        }
174
    }
175
176
    /**
177
     * Check if we have the full representation of our data object. We cannot
178
     * really know from the data object alone, but when this method is called
179
     * we should have all the data.
180
     *
181
     * @param \stdClass $data
182
     * @return boolean
183
     */
184
    protected function isInitialized($data)
185
    {
186
        return true;
187
    }
188
189
    /**
190
     * Total number of resources. Note that we don't get this number from API upfront,
191
     * so we have to fetch all the rows to find out.
192
     *
193
     * @link http://php.net/manual/en/countable.count.php
194
     * @return int
195
     */
196
    public function count()
197
    {
198
        return count($this->init()->resources);
199
    }
200
201
    /**
202
     * Magic!
203
     * @param string $key
204
     * @return mixed
205
     */
206
    public function __get($key)
207
    {
208
        if ($key === 'headers') {
209
            return $this->getHeaders();
210
        }
211
        if ($key === 'rows') {
212
            return $this;
213
        }
214
    }
215
}
216