Passed
Pull Request — master (#22)
by Thijs
01:50
created

WIQL::orWhere()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace TestMonitor\DevOps\Builders\WIQL;
4
5
use Closure;
6
7
class WIQL
8
{
9
    /**
10
     * @var string[]
11
     */
12
    protected $selects = [Field::ID];
13
14
    /**
15
     * @var string
16
     */
17
    protected $from = 'WorkItems';
18
19
    /**
20
     * @var array
21
     */
22
    protected $wheres = [];
23
24
    /**
25
     * @var array
26
     */
27
    protected $orders = [];
28
29
    /**
30
     * Set field selection.
31
     *
32
     * @param array $fields
33
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
34
     */
35 1
    public function select(array $fields): self
36
    {
37 1
        $this->selects = $fields;
38
39 1
        return $this;
40
    }
41
42
    /**
43
     * Set query source (WorkItems, workItemLinks).
44
     *
45
     * @param string $source
46
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
47
     */
48 1
    public function from(string $source): self
49
    {
50 1
        $this->from = $source;
51
52 1
        return $this;
53
    }
54
55
    /**
56
     * Add a new where condition.
57
     *
58
     * @param string|\Closure $field
59
     * @param mixed $operator
60
     * @param mixed $value
61
     * @param string $boolean
62
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
63
     */
64 10
    public function where(
65
        string|Closure $field,
66
        mixed $operator = Operator::EQUALS,
67
        mixed $value = null,
68
        string $boolean = Keyword::AND
69
    ): self {
70 10
        $this->wheres[] = compact('field', 'operator', 'value', 'boolean');
71
72 10
        return $this;
73
    }
74
75
    /**
76
     * Add a new where condition (using OR).
77
     *
78
     * @param string $field
79
     * @param mixed $operator
80
     * @param mixed $value
81
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
82
     */
83 4
    public function orWhere(string|Closure $field, mixed $operator = Operator::EQUALS, mixed $value = null): self
84
    {
85 4
        return $this->where($field, $operator, $value, Keyword::OR);
86
    }
87
88
    /**
89
     * Add new sort criteria.
90
     *
91
     * @param string $field
92
     * @param string $direction
93
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
94
     */
95 3
    public function orderBy(string $field, string $direction = 'ASC'): self
96
    {
97 3
        $this->orders[] = compact('field', 'direction');
98
99 3
        return $this;
100
    }
101
102
    /**
103
     * Executes the callback when value is true.
104
     *
105
     * @param mixed $value
106
     * @param callable $callback
107
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
108
     */
109 2
    public function when(mixed $value, callable $callback): self
110
    {
111 2
        $value = $value instanceof Closure ? $value($this) : $value;
112
113 2
        if ($value) {
114 1
            return $callback($this, $value) ?? $this;
115
        }
116
117 1
        return $this;
118
    }
119
120
    /**
121
     * Quotes a value based on its operator.
122
     *
123
     * @param string $operator
124
     * @param mixed $value
125
     * @return string
126
     */
127 10
    public function quote(string $operator, mixed $value): string
128
    {
129 10
        if (in_array($operator, [Operator::IN, Operator::NOT_IN], true)) {
130 1
            $values = implode(
131 1
                ', ',
132 1
                array_map(
133 1
                    fn ($value) => "'$value'",
134 1
                    (array) $value
135 1
                )
136 1
            );
137
138 1
            return "($values)";
139
        }
140
141 9
        return "'{$value}'";
142
    }
143
144
    /**
145
     * Generates conditions based on the available "wheres".
146
     *
147
     * @return string
148
     */
149 10
    public function getConditions(): string
150
    {
151 10
        $conditions = '';
152
153 10
        foreach ($this->wheres as $key => $condition) {
154 10
            $conditions .= $key !== array_key_first($this->wheres) ? " {$condition['boolean']} " : '';
155
156 10
            if ($condition['field'] instanceof Closure) {
157 2
                $conditions .= '(' . $condition['field'](new WIQL)->getConditions() . ')';
158
159 2
                continue;
160
            }
161
162 10
            $values = $this->quote($condition['operator'], $condition['value']);
163
164 10
            $conditions .= "{$condition['field']} {$condition['operator']} {$values}";
165
        }
166
167 10
        return $conditions;
168
    }
169
170
    /**
171
     * Generates the WIQL query.
172
     *
173
     * @return string
174
     */
175 20
    public function getQuery(): string
176
    {
177 20
        return trim(
178 20
            $this->buildSelect() . ' ' .
179 20
            $this->buildFrom() . ' ' .
180 20
            $this->buildWhere() . ' ' .
181 20
            $this->buildOrder()
182 20
        );
183
    }
184
185
    /**
186
     * Generate the SELECT part of the query.
187
     *
188
     * @return string
189
     */
190 20
    protected function buildSelect(): string
191
    {
192 20
        return 'SELECT ' . implode(', ', $this->selects);
193
    }
194
195
    /**
196
     * Generate the FROM part of the query.
197
     *
198
     * @return string
199
     */
200 20
    protected function buildFrom(): string
201
    {
202 20
        return "FROM {$this->from}";
203
    }
204
205
    /**
206
     * Generate the WHERE part of the query.
207
     *
208
     * @return string
209
     */
210 20
    protected function buildWhere(): string
211
    {
212 20
        if (empty($this->wheres)) {
213 10
            return '';
214
        }
215
216 10
        return 'WHERE ' . $this->getConditions();
217
    }
218
219
    /**
220
     * Generate the ORDER BY part of the query.
221
     *
222
     * @return string
223
     */
224 20
    protected function buildOrder(): string
225
    {
226 20
        if (empty($this->orders)) {
227 17
            return '';
228
        }
229
230 3
        $criteria = array_map(
231 3
            fn ($sort) => trim("{$sort['field']} {$sort['direction']}"),
232 3
            $this->orders
233 3
        );
234
235 3
        return 'ORDER BY ' . implode(', ', $criteria);
236
    }
237
}
238