Complex classes like OracleGrammar often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OracleGrammar, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class OracleGrammar extends Grammar |
||
11 | { |
||
12 | use OracleReservedWords; |
||
13 | |||
14 | /** |
||
15 | * The keyword identifier wrapper format. |
||
16 | * |
||
17 | * @var string |
||
18 | */ |
||
19 | protected $wrapper = '%s'; |
||
20 | |||
21 | /** |
||
22 | * @var string |
||
23 | */ |
||
24 | protected $schema_prefix = ''; |
||
25 | |||
26 | /** |
||
27 | * Compile an exists statement into SQL. |
||
28 | * |
||
29 | * @param \Illuminate\Database\Query\Builder $query |
||
30 | * @return string |
||
31 | */ |
||
32 | 3 | public function compileExists(Builder $query) |
|
33 | { |
||
34 | 3 | $q = clone $query; |
|
35 | 3 | $q->columns = []; |
|
36 | 3 | $q->selectRaw('1 as "exists"') |
|
37 | 3 | ->whereRaw("rownum = 1"); |
|
38 | |||
39 | 3 | return $this->compileSelect($q); |
|
40 | } |
||
41 | |||
42 | /** |
||
43 | * Compile a select query into SQL. |
||
44 | * |
||
45 | * @param \Illuminate\Database\Query\Builder |
||
46 | * @return string |
||
47 | */ |
||
48 | 159 | public function compileSelect(Builder $query) |
|
49 | { |
||
50 | 159 | if (is_null($query->columns)) { |
|
51 | $query->columns = ['*']; |
||
52 | } |
||
53 | |||
54 | 159 | $components = $this->compileComponents($query); |
|
55 | |||
56 | // If an offset is present on the query, we will need to wrap the query in |
||
57 | // a big "ANSI" offset syntax block. This is very nasty compared to the |
||
58 | // other database systems but is necessary for implementing features. |
||
59 | 159 | if ($this->isPaginationable($query, $components)) { |
|
60 | 12 | return $this->compileAnsiOffset($query, $components); |
|
61 | } |
||
62 | |||
63 | 150 | return trim($this->concatenate($components)); |
|
64 | } |
||
65 | |||
66 | /** |
||
67 | * @param Builder $query |
||
68 | * @param array $components |
||
69 | * @return bool |
||
70 | */ |
||
71 | 159 | protected function isPaginationable(Builder $query, array $components) |
|
72 | { |
||
73 | 159 | return ($query->limit > 0 || $query->offset > 0) && ! array_key_exists('lock', $components); |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * Create a full ANSI offset clause for the query. |
||
78 | * |
||
79 | * @param \Illuminate\Database\Query\Builder $query |
||
80 | * @param array $components |
||
81 | * @return string |
||
82 | */ |
||
83 | 12 | protected function compileAnsiOffset(Builder $query, $components) |
|
84 | { |
||
85 | 12 | $constraint = $this->compileRowConstraint($query); |
|
86 | |||
87 | 12 | $sql = $this->concatenate($components); |
|
88 | |||
89 | // We are now ready to build the final SQL query so we'll create a common table |
||
90 | // expression from the query and get the records with row numbers within our |
||
91 | // given limit and offset value that we just put on as a query constraint. |
||
92 | 12 | $temp = $this->compileTableExpression($sql, $constraint, $query); |
|
93 | |||
94 | 12 | return $temp; |
|
95 | } |
||
96 | |||
97 | /** |
||
98 | * Compile the limit / offset row constraint for a query. |
||
99 | * |
||
100 | * @param \Illuminate\Database\Query\Builder $query |
||
101 | * @return string |
||
102 | */ |
||
103 | 12 | protected function compileRowConstraint($query) |
|
104 | { |
||
105 | 12 | $start = $query->offset + 1; |
|
106 | |||
107 | 12 | if ($query->limit == 1) { |
|
108 | 6 | return "= 1"; |
|
109 | } |
||
110 | |||
111 | 6 | if ($query->limit > 1) { |
|
112 | 6 | $finish = $query->offset + $query->limit; |
|
113 | |||
114 | 6 | return "between {$start} and {$finish}"; |
|
115 | } |
||
116 | |||
117 | 3 | return ">= {$start}"; |
|
118 | } |
||
119 | |||
120 | /** |
||
121 | * Compile a common table expression for a query. |
||
122 | * |
||
123 | * @param string $sql |
||
124 | * @param string $constraint |
||
125 | * @param Builder $query |
||
126 | * @return string |
||
127 | */ |
||
128 | 12 | protected function compileTableExpression($sql, $constraint, $query) |
|
129 | { |
||
130 | 12 | if ($query->limit > 1) { |
|
131 | 6 | return "select t2.* from ( select rownum AS \"rn\", t1.* from ({$sql}) t1 ) t2 where t2.\"rn\" {$constraint}"; |
|
132 | } else { |
||
133 | 9 | return "select * from ({$sql}) where rownum {$constraint}"; |
|
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Compile a truncate table statement into SQL. |
||
139 | * |
||
140 | * @param \Illuminate\Database\Query\Builder $query |
||
141 | * @return array |
||
142 | */ |
||
143 | 3 | public function compileTruncate(Builder $query) |
|
144 | { |
||
145 | 3 | return ['truncate table ' . $this->wrapTable($query->from) => []]; |
|
146 | } |
||
147 | |||
148 | /** |
||
149 | * Wrap a table in keyword identifiers. |
||
150 | * |
||
151 | * @param \Illuminate\Database\Query\Expression|string $table |
||
152 | * @return string |
||
153 | */ |
||
154 | 201 | public function wrapTable($table) |
|
155 | { |
||
156 | 201 | if ($this->isExpression($table)) { |
|
157 | return $this->getValue($table); |
||
|
|||
158 | } |
||
159 | |||
160 | 201 | if (strpos(strtolower($table), ' as ') !== false) { |
|
161 | $table = str_replace(' as ', ' ', $table); |
||
162 | } |
||
163 | |||
164 | 201 | $tableName = $this->wrap($this->tablePrefix . $table, true); |
|
165 | 201 | $segments = explode(' ', $table); |
|
166 | 201 | if (count($segments) > 1) { |
|
167 | $tableName = $this->wrap($this->tablePrefix . $segments[0]) . ' ' . $segments[1]; |
||
168 | } |
||
169 | |||
170 | 201 | return $this->getSchemaPrefix() . $tableName; |
|
171 | } |
||
172 | |||
173 | /** |
||
174 | * Return the schema prefix. |
||
175 | * |
||
176 | * @return string |
||
177 | */ |
||
178 | 201 | public function getSchemaPrefix() |
|
179 | { |
||
180 | 201 | return ! empty($this->schema_prefix) ? $this->wrapValue($this->schema_prefix) . '.' : ''; |
|
181 | } |
||
182 | |||
183 | /** |
||
184 | * Set the schema prefix. |
||
185 | * |
||
186 | * @param string $prefix |
||
187 | */ |
||
188 | public function setSchemaPrefix($prefix) |
||
189 | { |
||
190 | $this->schema_prefix = $prefix; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Wrap a single string in keyword identifiers. |
||
195 | * |
||
196 | * @param string $value |
||
197 | * @return string |
||
198 | */ |
||
199 | 201 | protected function wrapValue($value) |
|
200 | { |
||
201 | 201 | if ($value === '*') { |
|
202 | 123 | return $value; |
|
203 | } |
||
204 | |||
205 | 201 | $value = $this->isReserved($value) ? Str::lower($value) : Str::upper($value); |
|
206 | |||
207 | 201 | return '"' . str_replace('"', '""', $value) . '"'; |
|
208 | } |
||
209 | |||
210 | /** |
||
211 | * Compile an insert and get ID statement into SQL. |
||
212 | * |
||
213 | * @param \Illuminate\Database\Query\Builder $query |
||
214 | * @param array $values |
||
215 | * @param string $sequence |
||
216 | * @return string |
||
217 | */ |
||
218 | 6 | public function compileInsertGetId(Builder $query, $values, $sequence = 'id') |
|
219 | { |
||
220 | 6 | if (empty($sequence)) { |
|
221 | $sequence = 'id'; |
||
222 | } |
||
223 | |||
224 | 6 | $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 4)[3]['object']; |
|
225 | |||
226 | 6 | if (method_exists($backtrace, 'getModel')) { |
|
227 | $model = $backtrace->getModel(); |
||
228 | if ($model->sequence && ! isset($values[$model->getKeyName()]) && $model->incrementing) { |
||
229 | $values[$sequence] = null; |
||
230 | } |
||
231 | } |
||
232 | |||
233 | 6 | return $this->compileInsert($query, $values) . ' returning ' . $this->wrap($sequence) . ' into ?'; |
|
234 | } |
||
235 | |||
236 | /** |
||
237 | * Compile an insert statement into SQL. |
||
238 | * |
||
239 | * @param \Illuminate\Database\Query\Builder $query |
||
240 | * @param array $values |
||
241 | * @return string |
||
242 | */ |
||
243 | 15 | public function compileInsert(Builder $query, array $values) |
|
244 | { |
||
245 | // Essentially we will force every insert to be treated as a batch insert which |
||
246 | // simply makes creating the SQL easier for us since we can utilize the same |
||
247 | // basic routine regardless of an amount of records given to us to insert. |
||
248 | 15 | $table = $this->wrapTable($query->from); |
|
249 | |||
250 | 15 | if (! is_array(reset($values))) { |
|
251 | 6 | $values = [$values]; |
|
252 | 6 | } |
|
253 | |||
254 | 15 | $columns = $this->columnize(array_keys(reset($values))); |
|
255 | |||
256 | // We need to build a list of parameter place-holders of values that are bound |
||
257 | // to the query. Each insert should have the exact same amount of parameter |
||
258 | // bindings so we can just go off the first list of values in this array. |
||
259 | 15 | $parameters = $this->parameterize(reset($values)); |
|
260 | |||
261 | 15 | $value = array_fill(0, count($values), "($parameters)"); |
|
262 | |||
263 | 15 | if (count($value) > 1) { |
|
264 | 3 | $insertQueries = []; |
|
265 | 3 | foreach ($value as $parameter) { |
|
266 | 3 | $parameter = (str_replace(['(', ')'], '', $parameter)); |
|
267 | 3 | $insertQueries[] = "select " . $parameter . " from dual "; |
|
268 | 3 | } |
|
269 | 3 | $parameters = implode('union all ', $insertQueries); |
|
270 | |||
271 | 3 | return "insert into $table ($columns) $parameters"; |
|
272 | } |
||
273 | 12 | $parameters = implode(', ', $value); |
|
274 | |||
275 | 12 | return "insert into $table ($columns) values $parameters"; |
|
276 | } |
||
277 | |||
278 | /** |
||
279 | * Compile an insert with blob field statement into SQL. |
||
280 | * |
||
281 | * @param \Illuminate\Database\Query\Builder $query |
||
282 | * @param array $values |
||
283 | * @param array $binaries |
||
284 | * @param string $sequence |
||
285 | * @return string |
||
286 | */ |
||
287 | 6 | public function compileInsertLob(Builder $query, $values, $binaries, $sequence = 'id') |
|
288 | { |
||
289 | 6 | if (empty($sequence)) { |
|
290 | $sequence = 'id'; |
||
291 | } |
||
292 | |||
293 | 6 | $table = $this->wrapTable($query->from); |
|
294 | |||
295 | 6 | if (! is_array(reset($values))) { |
|
296 | 6 | $values = [$values]; |
|
297 | 6 | } |
|
298 | |||
299 | 6 | if (! is_array(reset($binaries))) { |
|
300 | 6 | $binaries = [$binaries]; |
|
301 | 6 | } |
|
302 | |||
303 | 6 | $columns = $this->columnize(array_keys(reset($values))); |
|
304 | 6 | $binaryColumns = $this->columnize(array_keys(reset($binaries))); |
|
305 | 6 | $columns .= (empty($columns) ? '' : ', ') . $binaryColumns; |
|
306 | |||
307 | 6 | $parameters = $this->parameterize(reset($values)); |
|
308 | 6 | $binaryParameters = $this->parameterize(reset($binaries)); |
|
309 | |||
310 | 6 | $value = array_fill(0, count($values), "$parameters"); |
|
311 | 6 | $binaryValue = array_fill(0, count($binaries), str_replace('?', 'EMPTY_BLOB()', $binaryParameters)); |
|
312 | |||
313 | 6 | $value = array_merge($value, $binaryValue); |
|
314 | 6 | $parameters = implode(', ', array_filter($value)); |
|
315 | |||
316 | 6 | return "insert into $table ($columns) values ($parameters) returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?'; |
|
317 | } |
||
318 | |||
319 | /** |
||
320 | * Compile an update statement into SQL. |
||
321 | * |
||
322 | * @param \Illuminate\Database\Query\Builder $query |
||
323 | * @param array $values |
||
324 | * @param array $binaries |
||
325 | * @param string $sequence |
||
326 | * @return string |
||
327 | */ |
||
328 | 6 | public function compileUpdateLob(Builder $query, array $values, array $binaries, $sequence = 'id') |
|
329 | { |
||
330 | 6 | $table = $this->wrapTable($query->from); |
|
331 | |||
332 | // Each one of the columns in the update statements needs to be wrapped in the |
||
333 | // keyword identifiers, also a place-holder needs to be created for each of |
||
334 | // the values in the list of bindings so we can make the sets statements. |
||
335 | 6 | $columns = []; |
|
336 | |||
337 | 6 | foreach ($values as $key => $value) { |
|
338 | 3 | $columns[] = $this->wrap($key) . ' = ' . $this->parameter($value); |
|
339 | 6 | } |
|
340 | |||
341 | 6 | $columns = implode(', ', $columns); |
|
342 | |||
343 | // set blob variables |
||
344 | 6 | if (! is_array(reset($binaries))) { |
|
345 | 6 | $binaries = [$binaries]; |
|
346 | 6 | } |
|
347 | 6 | $binaryColumns = $this->columnize(array_keys(reset($binaries))); |
|
348 | 6 | $binaryParameters = $this->parameterize(reset($binaries)); |
|
349 | |||
350 | // create EMPTY_BLOB sql for each binary |
||
351 | 6 | $binarySql = []; |
|
352 | 6 | foreach ((array) $binaryColumns as $binary) { |
|
353 | 6 | $binarySql[] = "$binary = EMPTY_BLOB()"; |
|
354 | 6 | } |
|
355 | |||
356 | // prepare binary SQLs |
||
357 | 6 | if (count($binarySql)) { |
|
358 | 6 | $binarySql = (empty($columns) ? '' : ', ') . implode(',', $binarySql); |
|
359 | 6 | } |
|
360 | |||
361 | // If the query has any "join" clauses, we will setup the joins on the builder |
||
362 | // and compile them so we can attach them to this update, as update queries |
||
363 | // can get join statements to attach to other tables when they're needed. |
||
364 | 6 | if (isset($query->joins)) { |
|
365 | $joins = ' ' . $this->compileJoins($query, $query->joins); |
||
366 | } else { |
||
367 | 6 | $joins = ''; |
|
368 | } |
||
369 | |||
370 | // Of course, update queries may also be constrained by where clauses so we'll |
||
371 | // need to compile the where clauses and attach it to the query so only the |
||
372 | // intended records are updated by the SQL statements we generate to run. |
||
373 | 6 | $where = $this->compileWheres($query); |
|
374 | |||
375 | 6 | return "update {$table}{$joins} set $columns$binarySql $where returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?'; |
|
376 | } |
||
377 | |||
378 | /** |
||
379 | * Compile the lock into SQL. |
||
380 | * |
||
381 | * @param \Illuminate\Database\Query\Builder $query |
||
382 | * @param bool|string $value |
||
383 | * @return string |
||
384 | */ |
||
385 | 3 | protected function compileLock(Builder $query, $value) |
|
386 | { |
||
387 | 3 | if (is_string($value)) { |
|
388 | return $value; |
||
389 | } |
||
390 | |||
391 | 3 | if ($value) { |
|
392 | 3 | return 'for update'; |
|
393 | } |
||
394 | |||
395 | return ''; |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * Compile the "limit" portions of the query. |
||
400 | * |
||
401 | * @param \Illuminate\Database\Query\Builder $query |
||
402 | * @param int $limit |
||
403 | * @return string |
||
404 | */ |
||
405 | 12 | protected function compileLimit(Builder $query, $limit) |
|
409 | |||
410 | /** |
||
411 | * Compile the "offset" portions of the query. |
||
412 | * |
||
413 | * @param \Illuminate\Database\Query\Builder $query |
||
414 | * @param int $offset |
||
415 | * @return string |
||
416 | */ |
||
417 | 3 | protected function compileOffset(Builder $query, $offset) |
|
418 | { |
||
419 | 3 | return ''; |
|
420 | } |
||
421 | |||
422 | /** |
||
423 | * Compile a "where date" clause. |
||
424 | * |
||
425 | * @param \Illuminate\Database\Query\Builder $query |
||
426 | * @param array $where |
||
427 | * @return string |
||
428 | */ |
||
429 | 3 | protected function whereDate(Builder $query, $where) |
|
435 | |||
436 | /** |
||
437 | * Compile a date based where clause. |
||
438 | * |
||
439 | * @param string $type |
||
440 | * @param \Illuminate\Database\Query\Builder $query |
||
441 | * @param array $where |
||
442 | * @return string |
||
443 | */ |
||
444 | 9 | protected function dateBasedWhere($type, Builder $query, $where) |
|
450 | } |
||
451 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.