Completed
Push — master ( 2e5c63...8dada4 )
by Vladimir
01:35
created

SoqlQuery::formatAssociativeArray()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 5
eloc 8
nc 4
nop 2
1
<?php
2
3
/**
4
 * This file contains the SoqlQuery class and the respective constants and default values that belong to SoQL.
5
 *
6
 * @copyright 2015 Vladimir Jimenez
7
 * @license   https://github.com/allejo/PhpSoda/blob/master/LICENSE.md MIT
8
 */
9
10
namespace allejo\Socrata;
11
12
/**
13
 * An object provided for the creation and handling of SoQL queries in an object-oriented fashion.
14
 *
15
 * @package allejo\Socrata
16
 * @since   0.1.0
17
 */
18
class SoqlQuery
19
{
20
    /**
21
     * The default delimiter used to separate multiple values.
22
     */
23
    const DELIMITER = ',';
24
25
    /**
26
     * The SELECT clause in SoQL
27
     */
28
    const SELECT_KEY = '$select';
29
30
    /**
31
     * The WHERE clause in SoQL
32
     */
33
    const WHERE_KEY = '$where';
34
35
    /**
36
     * The ORDER clause in SoQL
37
     */
38
    const ORDER_KEY = '$order';
39
40
    /**
41
     * The GROUP clause in SoQL
42
     */
43
    const GROUP_KEY = '$group';
44
45
    /**
46
     * The LIMIT clause in SoQL
47
     */
48
    const LIMIT_KEY = '$limit';
49
50
    /**
51
     * The HAVING clause in SoQL
52
     */
53
    const HAVING_KEY = '$having';
54
55
    /**
56
     * The OFFSET clause in SoQL
57
     */
58
    const OFFSET_KEY = '$offset';
59
60
    /**
61
     * The SEARCH clause in SoQL
62
     */
63
    const SEARCH_KEY = '$q';
64
65
    /**
66
     * The default value for the `$select` clause in a SoQL query. By default, select all the columns
67
     */
68
    const DEFAULT_SELECT = '*';
69
70
    /**
71
     * The default order for the `$order` clause in a SoQL query. By default, order in ascending order
72
     */
73
    const DEFAULT_ORDER_DIRECTION = SoqlOrderDirection::ASC;
74
75
    /**
76
     * This array contains all of the parts to a SoqlQuery being converted into a URL where the key of an element is the
77
     * SoQL clause (e.g. $select) and the value of an element is the value to the SoQL clause (e.g. *).
78
     *
79
     * @var string[]
80
     */
81
    private $queryElements;
82
83
    /**
84
     * Write a SoQL query by chaining functions. This object will handle encoding the final query in order for it to be
85
     * used properly as a URL. By default a SoqlQuery will select all columns (excluding socrata columns; e.g. :id) and
86
     * sort by `:id` in ascending order.
87
     *
88
     * @since 0.1.0
89
     */
90
    public function __construct () {}
91
92
    /**
93
     * Convert the current information into a URL encoded query that can be appended to the domain
94
     *
95
     * @since 0.1.0
96
     *
97
     * @return string The SoQL query ready to be appended to a URL
98
     */
99
    public function __tostring ()
100
    {
101
        if (is_null($this->queryElements))
102
        {
103
            return "";
104
        }
105
106
        $query = [];
107
108
        foreach ($this->queryElements as $soqlKey => $value)
109
        {
110
            $value = (is_array($value)) ? implode(self::DELIMITER, $value) : $value;
111
112
            $query[] = sprintf("%s=%s", $soqlKey, $value);
113
        }
114
115
        return implode("&", $query);
116
    }
117
118
    /**
119
     * Select only specific columns in your Soql Query. When this function is given no parameters or is not used in a
120
     * query, the Soql Query will return all of the columns by default.
121
     *
122
     * ```php
123
     * // These are all valid usages
124
     * $soqlQuery->select();
125
     * $soqlQuery->select("foo", "bar", "baz");
126
     * $soqlQuery->select(array("foo" => "foo_alias", "bar" => "bar_alias", "baz"));
127
     * ```
128
     *
129
     * @link    https://dev.socrata.com/docs/queries/select.html SoQL $select Parameter
130
     *
131
     * @param   array|mixed $columns   The columns to select from the dataset. The columns can be specified as an array
132
     *                                 of values or it can be specified as multiple parameters separated by commas.
133
     *
134
     * @since   0.1.0
135
     *
136
     * @return  $this       A SoqlQuery object that can continue to be chained
137
     */
138
    public function select ($columns = self::DEFAULT_SELECT)
139
    {
140
        if (func_num_args() == 1)
141
        {
142
            $this->queryElements[self::SELECT_KEY] = (is_array($columns)) ? $this->formatAssociativeArray("%s AS %s", $columns) : array($columns);
143
        }
144
        else if (func_num_args() > 1)
145
        {
146
            $this->queryElements[self::SELECT_KEY] = func_get_args();
147
        }
148
149
        return $this;
150
    }
151
152
    /**
153
     * Create an array of values that have already been formatted and are ready to be converted into a comma separated
154
     * list that will be used as a parameter for selectors such was `$select`, `$order`, or `$group` in SoQL
155
     *
156
     * @param   string $format The format used in sprintf() for keys and values of an array to be formatted to
157
     * @param   array  $array  The array that will be formatted appropriately for usage within this class
158
     *
159
     * @since   0.1.0
160
     *
161
     * @return  array
162
     */
163
    private function formatAssociativeArray ($format, $array)
164
    {
165
        $formattedValues = array();
166
167
        foreach ($array as $key => $value)
168
        {
169
            if(is_string($key) && !is_null($value))
170
            {
171
                $formattedValues[] = rawurlencode(sprintf($format, trim($key), trim($value)));
172
            }
173
            else
174
            {
175
                $formattedValues[] = is_string($key) ? $key : $value;
176
            }
177
        }
178
179
        return $formattedValues;
180
    }
181
182
    /**
183
     * Create a filter to selectively choose data based on certain parameters.
184
     *
185
     * Multiple calls to this function in a chain will overwrite the previous statement. To combine multiple where
186
     * clauses, use the supported SoQL operators; e.g. `magnitude > 3.0 AND source = 'pr'`
187
     *
188
     * @link    https://dev.socrata.com/docs/queries/where.html SoQL $where Parameter
189
     *
190
     * @param   string $statement The `where` clause that will be used to filter data
191
     *
192
     * @since   0.1.0
193
     *
194
     * @return  $this  A SoqlQuery object that can continue to be chained
195
     */
196
    public function where ($statement)
197
    {
198
        $this->queryElements[self::WHERE_KEY] = rawurlencode($statement);
199
200
        return $this;
201
    }
202
203
    /**
204
     * Create a filter to aggregate your results using boolean operators, similar to the HAVING clause in SQL.
205
     *
206
     * @link    https://dev.socrata.com/docs/queries/having.html SoQL $having Parameter
207
     *
208
     * @param   string $statement The `having` clause that will be used to filter data
209
     *
210
     * @since   0.2.0
211
     *
212
     * @return  $this  A SoqlQuery object that can continue to be chained
213
     */
214
    public function having ($statement)
215
    {
216
        $this->queryElements[self::HAVING_KEY] = rawurlencode($statement);
217
218
        return $this;
219
    }
220
221
    /**
222
     * Determines the order and the column the results should be sorted by. This function may be used more than once in
223
     * a chain so duplicate entries in the first column will be sorted by the second specified column specified. If
224
     * this
225
     * function is called more than once in a chain, the order does matter in which order() you call first.
226
     *
227
     * @link    https://dev.socrata.com/changelog/2015/04/27/new-higher-performance-apis.html New Higher Performance API
228
     * @link    https://dev.socrata.com/docs/queries/order.html SoQL $order Parameter
229
     *
230
     * @param   string $column      The column(s) that determines how the results should be sorted. This information
231
     *                              can be given as an array of values, a single column, or a comma separated string.
232
     *                              In order to support sorting by multiple columns, you need to use the latest version
233
     *                              of the dataset API.
234
     * @param   string $direction   The direction the results should be sorted in, either ascending or descending. The
235
     *                              {@link SoqlOrderDirection} class provides constants to use should these values ever
236
     *                              change in the future. The only accepted values are: `ASC` and `DESC`
237
     *
238
     * @see     SoqlOrderDirection  View convenience constants
239
     *
240
     * @since   0.1.0
241
     *
242
     * @return  $this   A SoqlQuery object that can continue to be chained
243
     */
244
    public function order ($column, $direction = self::DEFAULT_ORDER_DIRECTION)
245
    {
246
        $this->queryElements[self::ORDER_KEY][] = rawurlencode($column . " " . $direction);
247
248
        return $this;
249
    }
250
251
    /**
252
     * Group the resulting dataset based on a specific column. This function must be used in conjunction with
253
     * `select()`.
254
     *
255
     * For example, to find the strongest earthquake by region, we want to group() by region and provide a select of
256
     * region, MAX(magnitude).
257
     *
258
     * ```php
259
     * $soql->select("region", "MAX(magnitude)")->group("region");
260
     * ```
261
     *
262
     * @link    https://dev.socrata.com/docs/queries/group.html  The $group Parameter
263
     *
264
     * @param   string $column The column that will be used to group the dataset
265
     *
266
     * @since   0.1.0
267
     *
268
     * @return  $this   A SoqlQuery object that can continue to be chained
269
     */
270
    public function group ($column)
271
    {
272
        $this->queryElements[self::GROUP_KEY][] = $column;
273
274
        return $this;
275
    }
276
277
    /**
278
     * Set the amount of results that can be retrieved from a dataset per query.
279
     *
280
     * @link    https://dev.socrata.com/docs/queries/limit.html  SoQL $limit Parameter
281
     *
282
     * @param   int $limit The number of results the dataset should be limited to when returned
283
     *
284
     * @throws  \InvalidArgumentException  If the given argument is not an integer
285
     * @throws  \OutOfBoundsException      If the given argument is less than 0
286
     *
287
     * @since   0.1.0
288
     *
289
     * @return  $this          A SoqlQuery object that can continue to be chained
290
     */
291
    public function limit ($limit)
292
    {
293
        $this->handleInteger("limit", $limit);
294
295
        $this->queryElements[self::LIMIT_KEY] = $limit;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Analyze a given value and ensure the value fits the criteria set by the Socrata API
302
     *
303
     * @param   string $variable The literal name of this field
304
     * @param   int    $number   The value to analyze
305
     *
306
     * @since   0.1.0
307
     *
308
     * @throws  \InvalidArgumentException         If the given argument is not an integer
309
     * @throws  \OutOfBoundsException             If the given argument is less than 0
310
     */
311
    private function handleInteger ($variable, $number)
312
    {
313
        if (!is_integer($number))
314
        {
315
            throw new \InvalidArgumentException(sprintf("The %s must be an integer", $variable));
316
        }
317
318
        if ($number < 0)
319
        {
320
            $message = sprintf("The %s cannot be less than 0.", $variable);
321
322
            throw new \OutOfBoundsException($message, 1);
323
        }
324
    }
325
326
    /**
327
     * The offset is the number of records into a dataset that you want to start, indexed at 0. For example, to retrieve
328
     * the “4th page” of records (records 151 - 200) where you are using limit() to page 50 records at a time, you’d ask
329
     * for an $offset of 150.
330
     *
331
     * @link    https://dev.socrata.com/docs/queries/offset.html  SoQL $offset Parameter
332
     *
333
     * @param   int $offset The number of results the dataset should be offset to when returned
334
     *
335
     * @throws  \InvalidArgumentException  If the given argument is not an integer
336
     * @throws  \OutOfBoundsException      If the given argument is less than 0
337
     *
338
     * @since   0.1.0
339
     *
340
     * @return  $this           A SoqlQuery object that can continue to be chained
341
     */
342
    public function offset ($offset)
343
    {
344
        $this->handleInteger("offset", $offset);
345
346
        $this->queryElements[self::OFFSET_KEY] = $offset;
347
348
        return $this;
349
    }
350
351
    /**
352
     * Search the entire dataset for a specified string. Think of this as a search engine instead of performing a SQL
353
     * query.
354
     *
355
     * @param   string $needle The phrase to search for
356
     *
357
     * @since   0.1.0
358
     *
359
     * @return  $this            A SoqlQuery object that can continue to be chained
360
     */
361
    public function fullTextSearch ($needle)
362
    {
363
        $this->queryElements[self::SEARCH_KEY] = rawurlencode($needle);
364
365
        return $this;
366
    }
367
}
368