1
2/**
3 * This file is part of the Phalcon Framework.
4 *
5 * (c) Phalcon Team <team@phalcon.io>
6 *
7 * For the full copyright and license information, please view the LICENSE.txt
8 * file that was distributed with this source code.
9 *
10 * Implementation of this file has been influenced by AtlasPHP
11 *
12 * @link    https://github.com/atlasphp/Atlas.Query
13 * @license https://github.com/atlasphp/Atlas.Qyert/blob/1.x/LICENSE.md
14 */
15
16namespace Phalcon\DataMapper\Query;
17
18use Phalcon\Helper\Arr;
19
20/**
21 * Class AbstractConditions
22 */
23abstract class AbstractConditions extends AbstractQuery
24{
25    /**
26     * Sets the `LIMIT` clause
27     *
28     * @param int $limit
29     *
30     * @return AbstractConditions
31     */
32    public function limit(int limit) -> <AbstractConditions>
33    {
34        let this->store["LIMIT"] = limit;
35
36        return this;
37    }
38
39    /**
40     * Sets the `OFFSET` clause
41     *
42     * @param int $offset
43     *
44     * @return AbstractConditions
45     */
46    public function offset(int offset) -> <AbstractConditions>
47    {
48        let this->store["OFFSET"] = offset;
49
50        return this;
51    }
52
53    /**
54     * Sets a `AND` for a `WHERE` condition
55     *
56     * @param string     $condition
57     * @param mixed|null $value
58     * @param int        $type
59     *
60     * @return AbstractConditions
61     */
62    public function andWhere(
63        string condition,
64        var value = null,
65        int type = -1
66    ) -> <AbstractConditions> {
67        this->where(condition, value, type);
68
69        return this;
70    }
71
72    /**
73     * Concatenates to the most recent `WHERE` clause
74     *
75     * @param string     $condition
76     * @param mixed|null $value
77     * @param int        $type
78     *
79     * @return AbstractConditions
80     */
81    public function appendWhere(
82        string condition,
83        var value = null,
84        int type = -1
85    ) -> <AbstractConditions> {
86        this->appendCondition("WHERE", condition, value, type);
87
88        return this;
89    }
90
91    /**
92     * Sets the `ORDER BY`
93     *
94     * @param array|string $orderBy
95     *
96     * @return AbstractConditions
97     */
98    public function orderBy(var orderBy) -> <AbstractConditions>
99    {
100        this->processValue("ORDER", orderBy);
101
102        return this;
103    }
104
105    /**
106     * Sets a `OR` for a `WHERE` condition
107     *
108     * @param string     $condition
109     * @param mixed|null $value
110     * @param int        $type
111     *
112     * @return AbstractConditions
113     */
114    public function orWhere(
115        string condition,
116        var value = null,
117        int type = -1
118    ) -> <AbstractConditions> {
119        this->addCondition("WHERE", "OR ", condition, value, type);
120
121        return this;
122    }
123
124    /**
125     * Sets a `WHERE` condition
126     *
127     * @param string     $condition
128     * @param mixed|null $value
129     * @param int        $type
130     *
131     * @return AbstractConditions
132     */
133    public function where(
134        string condition,
135        var value = null,
136        int type = -1
137    ) -> <AbstractConditions> {
138        this->addCondition("WHERE", "AND ", condition, value, type);
139
140        return this;
141    }
142
143    /**
144     * @param array $columnsValues
145     *
146     * @return AbstractConditions
147     */
148    public function whereEquals(array columnsValues) -> <AbstractConditions>
149    {
150        var key, value;
151
152        for key, value in columnsValues {
153            if is_numeric(key) {
154                this->where(value);
155            } elseif null === value {
156                this->where(key . " IS NULL");
157            } elseif typeof value === "array"  {
158                this->where(key . " IN ", value);
159            } else {
160                this->where(key . " = ", value);
161            }
162        }
163
164        return this;
165    }
166
167    /**
168     * Appends a conditional
169     *
170     * @param string     $store
171     * @param string     $andor
172     * @param string     $condition
173     * @param mixed|null $value
174     * @param int        $type
175     */
176    protected function addCondition(
177        string store,
178        string andor,
179        string condition,
180        var value = null,
181        int type = -1
182    ) -> void {
183        if !empty value {
184            let condition .= this->bindInline(value, type);
185        }
186
187        if empty this->store[store] {
188            let andor = "";
189        }
190
191        let this->store[store][] = andor . condition;
192    }
193
194    /**
195     * Builds a `BY` list
196     *
197     * @param string $type
198     *
199     * @return string
200     */
201    protected function buildBy(string type) -> string
202    {
203        if empty this->store[type] {
204            return "";
205        }
206
207        return " " . type . " BY"
208            . this->indent(this->store[type], ",");
209    }
210
211    /**
212     * Builds the conditional string
213     *
214     * @param string $type
215     *
216     * @return string
217     */
218    protected function buildCondition(string type) -> string
219    {
220        if empty this->store[type] {
221            return "";
222        }
223
224        return " " . type
225            . this->indent(this->store[type]);
226    }
227
228    /**
229     * Builds the early `LIMIT` clause - MS SQLServer
230     *
231     * @return string
232     */
233    protected function buildLimitEarly() -> string
234    {
235        string limit = "";
236
237        if (
238            "sqlsrv" === this->connection->getDriverName() &&
239            this->store["LIMIT"] > 0 &&
240            0 === this->store["OFFSET"]
241        ) {
242            let limit = " TOP " . this->store["LIMIT"];
243        }
244
245        return limit;
246    }
247
248    /**
249     * Builds the `LIMIT` clause
250     *
251     * @return string
252     */
253    protected function buildLimit() -> string
254    {
255        var method, suffix;
256
257        let suffix = this->connection->getDriverName();
258
259        if "sqlsrv" !== suffix {
260            let suffix = "common";
261        }
262
263        let method = "buildLimit" . ucfirst(suffix);
264
265        return this->{method}();
266    }
267
268    /**
269     * Builds the `LIMIT` clause for all drivers
270     *
271     * @return string
272     */
273
274    protected function buildLimitCommon() -> string
275    {
276        string limit = "";
277
278        if 0 !== this->store["LIMIT"] {
279            let limit .= "LIMIT " . this->store["LIMIT"];
280        }
281
282        if 0 !== this->store["OFFSET"] {
283            let limit .= " OFFSET " . this->store["OFFSET"];
284        }
285
286        if "" !== limit {
287            let limit = " " . ltrim(limit);
288        }
289
290        return limit;
291    }
292
293    /**
294     * Builds the `LIMIT` clause for MSSQLServer
295     *
296     * @return string
297     */
298    protected function buildLimitSqlsrv() -> string
299    {
300        string limit = "";
301
302        if this->store["LIMIT"] > 0 && this->store["OFFSET"] > 0 {
303            let limit = " OFFSET " . this->store["OFFSET"] . " ROWS"
304                . " FETCH NEXT " . this->store["LIMIT"] . " ROWS ONLY";
305        }
306
307        return limit;
308    }
309
310    /**
311     * Concatenates a conditional
312     *
313     * @param string $store
314     * @param string $condition
315     * @param mixed  $value
316     * @param int    $type
317     */
318    protected function appendCondition(
319        string store,
320        string condition,
321        var value = null,
322        int type = -1
323    ) -> void {
324        var key;
325
326        if !empty value {
327            let condition .= this->bindInline(value, type);
328        }
329
330        if empty this->store[store] {
331            let this->store[store][] = "";
332        }
333
334        let key = Arr::lastKey(this->store[store]);
335
336        let this->store[store][key] = this->store[store][key] . condition;
337    }
338
339    /**
340     * Processes a value (array or string) and merges it with the store
341     *
342     * @param string       $store
343     * @param array|string $data
344     */
345    protected function processValue(string store, var data) -> void
346    {
347        if typeof data === "string" {
348            let data = [data];
349        }
350
351        if typeof data === "array" {
352            let this->store[store] = array_merge(
353                this->store[store],
354                data
355            );
356        }
357    }
358}
359