Passed
Pull Request — master (#21)
by Thijs
02:17
created

WIQL::quote()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 15
ccs 11
cts 11
cp 1
crap 2
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 $field
59
     * @param mixed $operator
60
     * @param mixed $value
61
     * @param string $boolean
62
     * @return \TestMonitor\DevOps\Builders\WIQL\WIQL
63
     */
64 8
    public function where(
65
        string $field,
66
        mixed $operator = Operator::EQUALS,
67
        mixed $value = null,
68
        string $boolean = Keyword::AND
69
    ): self {
70 8
        $this->wheres[] = compact('field', 'operator', 'value', 'boolean');
71
72 8
        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 2
    public function orWhere(string $field, mixed $operator = Operator::EQUALS, mixed $value = null): self
84
    {
85 2
        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 8
    protected function quote(string $operator, mixed $value): string
128
    {
129 8
        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 7
        return "'{$value}'";
142
    }
143
144
    /**
145
     * Generates the WIQL query.
146
     *
147
     * @return string
148
     */
149 18
    public function getQuery(): string
150
    {
151 18
        return trim(
152 18
            $this->buildSelect() . ' ' .
153 18
            $this->buildFrom() . ' ' .
154 18
            $this->buildWhere() . ' ' .
155 18
            $this->buildOrder()
156 18
        );
157
    }
158
159
    /**
160
     * Generate the SELECT part of the query.
161
     *
162
     * @return string
163
     */
164 18
    protected function buildSelect(): string
165
    {
166 18
        return 'SELECT ' . implode(', ', $this->selects);
167
    }
168
169
    /**
170
     * Generate the FROM part of the query.
171
     *
172
     * @return string
173
     */
174 18
    protected function buildFrom(): string
175
    {
176 18
        return "FROM {$this->from}";
177
    }
178
179
    /**
180
     * Generate the WHERE part of the query.
181
     *
182
     * @return string
183
     */
184 18
    protected function buildWhere(): string
185
    {
186 18
        if (empty($this->wheres)) {
187 10
            return '';
188
        }
189
190 8
        $wiql = 'WHERE ';
191
192 8
        foreach ($this->wheres as $key => $condition) {
193 8
            $values = $this->quote($condition['operator'], $condition['value']);
194
195 8
            $wiql .= $key !== array_key_first($this->wheres) ? " {$condition['boolean']} " : '';
196
197 8
            $wiql .= "{$condition['field']} {$condition['operator']} {$values}";
198
        }
199
200 8
        return $wiql;
201
    }
202
203
    /**
204
     * Generate the ORDER BY part of the query.
205
     *
206
     * @return string
207
     */
208 18
    protected function buildOrder(): string
209
    {
210 18
        if (empty($this->orders)) {
211 15
            return '';
212
        }
213
214 3
        $criteria = array_map(
215 3
            fn ($sort) => trim("{$sort['field']} {$sort['direction']}"),
216 3
            $this->orders
217 3
        );
218
219 3
        return 'ORDER BY ' . implode(', ', $criteria);
220
    }
221
}
222