Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
PaginatedResult
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 8
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 first
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 hasFirst
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 next
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 hasNext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isLast
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isPaginated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 parsePaginationHeaders
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2namespace Ably\Models;
3
4use Ably\Exceptions\AblyException;
5
6/**
7 * Provides automatic pagination for applicable requests
8 *
9 * Requests for channel history, channel presence and app stats are wrapped in this class automatically.
10 */
11class PaginatedResult {
12
13    protected $ably;
14    protected $method;
15    protected $path;
16    protected $requestHeaders;
17    protected $model;
18    protected $cipherParams;
19    protected $paginationHeaders = false;
20    protected $response;
21
22    /**
23     * @var array Array of returned models (either Message, PresenceMessage or Stats)
24     */
25    public $items = [];
26
27    /**
28     * Constructor.
29     * @param \Ably\AblyRest $ably Ably API instance
30     * @param mixed $model Name of a class that will be instantiated for returned results. It must implement a fromJSON() method.
31     * @param CipherParams|null $cipherParams Optional cipher parameters if data should be decoded
32     * @param string $method HTTP method
33     * @param string $path Request path
34     * @param array $params Parameters to be sent with the request
35     * @param array $headers Headers to be sent with the request
36     * @throws AblyException
37     */
38    public function __construct( \Ably\AblyRest $ably, $model, $cipherParams, $method, $path, $params = [], $headers = [] ) {
39        $this->ably = $ably;
40        $this->model = $model;
41        $this->cipherParams = $cipherParams;
42        $this->requestHeaders = $headers;
43        $this->method = $method;
44        $this->path = $path;
45
46        $response = $this->ably->requestInternal( $method, $path, $headers, $params, $withHeaders = true );
47        $this->response = $response;
48
49        $body = isset( $response['body'] ) ? $response['body'] : [];
50        if ( is_object( $body ) ) $body = [ $body ];
51
52        if ( is_array( $body ) ) {
53
54            $transformedArray = [];
55
56            foreach ($body as $data) {
57
58                $instance = new $model;
59
60                if ( !method_exists( $model, 'fromJSON' ) ) {
61                    throw new AblyException( 'Invalid model class provided: ' . $model . '. The model needs to implement fromJSON method.' );
62                }
63                if ( !empty( $cipherParams ) ) {
64                    $instance->setCipherParams( $cipherParams );
65                }
66                $instance->fromJSON( $data );
67
68                $transformedArray[] = $instance;
69            }
70
71            $this->items = $transformedArray;
72            $this->parsePaginationHeaders( $response['headers'] );
73        }
74    }
75
76    /**
77     * Fetches the first page of results
78     * @return PaginatedResult Returns self if the current page is the first
79     */
80    public function first() {
81        if (isset($this->paginationHeaders['first'])) {
82            return new PaginatedResult( $this->ably, $this->model, $this->cipherParams, $this->method, $this->paginationHeaders['first'] );
83        } else {
84            return null;
85        }
86    }
87
88    /**
89     * @return boolean Whether there is a first page
90     */
91    public function hasFirst() {
92        return $this->isPaginated() && isset($this->paginationHeaders['first']);
93    }
94
95    /**
96     * Fetches the next page of results
97     * @return PaginatedResult|null Next page or null if the current page is the last
98     */
99    public function next() {
100        if ($this->isPaginated() && isset($this->paginationHeaders['next'])) {
101            return new PaginatedResult( $this->ably, $this->model, $this->cipherParams, $this->method, $this->paginationHeaders['next'] );
102        } else {
103            return null;
104        }
105    }
106
107    /**
108     * @return boolean Whether there is a next page
109     */
110    public function hasNext() {
111        return $this->isPaginated() && isset($this->paginationHeaders['next']);
112    }
113
114    /**
115     * @return boolean Whether the current page is the last, always true for single-page results
116     */
117    public function isLast() {
118        if (!$this->isPaginated() || !isset($this->paginationHeaders['next']) ) {
119            return true;
120        } else {
121            return false;
122        }
123    }
124
125    /**
126     * @return boolean Whether the fetched results can be paginated (pagination headers received)
127     */
128    public function isPaginated() {
129        return is_array($this->paginationHeaders) && count($this->paginationHeaders);
130    }
131
132    /**
133     * Parses HTTP headers for pagination links
134     */
135    private function parsePaginationHeaders($headers) {
136
137        $path = preg_replace('/\/[^\/]*$/', '/', $this->path);
138
139        preg_match_all('/Link: *\<([^\>]*)\>; *rel="([^"]*)"/', $headers, $matches, PREG_SET_ORDER);
140
141        if (!$matches) return;
142
143        $this->paginationHeaders = [];
144
145        foreach ($matches as $m) {
146            $link = $m[1];
147            $rel =  $m[2];
148
149            if (substr($link, 0, 2) != './') {
150                throw new AblyException( "Server error - only relative URLs are supported in pagination" );
151            }
152
153            $this->paginationHeaders[$rel] = $path.substr($link, 2);
154        }
155    }
156}