Passed
Pull Request — master (#21)
by Thijs
15:18
created

WIQL::getQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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