AbstractQueryFactory::getColumnsList()   F
last analyzed

Complexity

Conditions 18
Paths 282

Size

Total Lines 97
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 97
rs 3.6714
c 0
b 0
f 0
cc 18
eloc 59
nc 282
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Mouf\Database\TDBM\QueryFactory;
4
5
use Doctrine\DBAL\Schema\Schema;
6
use Mouf\Database\TDBM\OrderByAnalyzer;
7
use Mouf\Database\TDBM\TDBMInvalidArgumentException;
8
use Mouf\Database\TDBM\TDBMService;
9
use Mouf\Database\TDBM\UncheckedOrderBy;
10
11
abstract class AbstractQueryFactory implements QueryFactory
12
{
13
    /**
14
     * @var TDBMService
15
     */
16
    protected $tdbmService;
17
18
    /**
19
     * @var Schema
20
     */
21
    protected $schema;
22
23
    /**
24
     * @var OrderByAnalyzer
25
     */
26
    protected $orderByAnalyzer;
27
28
    /**
29
     * @var string|UncheckedOrderBy|null
30
     */
31
    protected $orderBy;
32
33
    protected $magicSql;
34
    protected $magicSqlCount;
35
    protected $columnDescList;
36
37
    /**
38
     * @param TDBMService $tdbmService
39
     */
40
    public function __construct(TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, $orderBy)
41
    {
42
        $this->tdbmService = $tdbmService;
43
        $this->schema = $schema;
44
        $this->orderByAnalyzer = $orderByAnalyzer;
45
        $this->orderBy = $orderBy;
46
    }
47
48
    /**
49
     * Returns the column list that must be fetched for the SQL request.
50
     *
51
     * Note: MySQL dictates that ORDER BYed columns should appear in the SELECT clause.
52
     *
53
     * @param string                       $mainTable
54
     * @param array                        $additionalTablesFetch
55
     * @param string|UncheckedOrderBy|null $orderBy
56
     *
57
     * @return array
58
     *
59
     * @throws \Doctrine\DBAL\Schema\SchemaException
60
     */
61
    protected function getColumnsList(string $mainTable, array $additionalTablesFetch = array(), $orderBy = null)
62
    {
63
        // From the table name and the additional tables we want to fetch, let's build a list of all tables
64
        // that must be part of the select columns.
65
66
        $connection = $this->tdbmService->getConnection();
67
68
        $tableGroups = [];
69
        $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($mainTable);
70
        $tableGroupName = $this->getTableGroupName($allFetchedTables);
71
        foreach ($allFetchedTables as $table) {
72
            $tableGroups[$table] = $tableGroupName;
73
        }
74
75
        $columnsList = [];
76
        $columnDescList = [];
77
        $sortColumn = 0;
78
        $reconstructedOrderBy = null;
79
80
        if (is_string($orderBy)) {
81
            $orderBy = trim($orderBy);
82
            if ($orderBy === '') {
83
                $orderBy = null;
84
            }
85
        }
86
87
        // Now, let's deal with "order by columns"
88
        if ($orderBy !== null) {
89
            if ($orderBy instanceof UncheckedOrderBy) {
90
                $securedOrderBy = false;
91
                $orderBy = $orderBy->getOrderBy();
92
                $reconstructedOrderBy = $orderBy;
93
            } else {
94
                $securedOrderBy = true;
95
                $reconstructedOrderBys = [];
96
            }
97
            $orderByColumns = $this->orderByAnalyzer->analyzeOrderBy($orderBy);
98
99
            // If we sort by a column, there is a high chance we will fetch the bean containing this column.
100
            // Hence, we should add the table to the $additionalTablesFetch
101
            foreach ($orderByColumns as $orderByColumn) {
102
                if ($orderByColumn['type'] === 'colref') {
103
                    if ($orderByColumn['table'] !== null) {
104
                        $additionalTablesFetch[] = $orderByColumn['table'];
105
                    }
106
                    if ($securedOrderBy) {
107
                        $reconstructedOrderBys[] = ($orderByColumn['table'] !== null ? $connection->quoteIdentifier($orderByColumn['table']).'.' : '').$connection->quoteIdentifier($orderByColumn['column']).' '.$orderByColumn['direction'];
0 ignored issues
show
Bug introduced by
The variable $reconstructedOrderBys does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
108
                    }
109
                } elseif ($orderByColumn['type'] === 'expr') {
110
                    $sortColumnName = 'sort_column_'.$sortColumn;
111
                    $columnsList[] = $orderByColumn['expr'].' as '.$sortColumnName;
112
                    $columnDescList[] = [
113
                        'tableGroup' => null,
114
                    ];
115
                    ++$sortColumn;
116
117
                    if ($securedOrderBy) {
118
                        throw new TDBMInvalidArgumentException('Invalid ORDER BY column: "'.$orderByColumn['expr'].'". If you want to use expression in your ORDER BY clause, you must wrap them in a UncheckedOrderBy object. For instance: new UncheckedOrderBy("col1 + col2 DESC")');
119
                    }
120
                }
121
            }
122
123
            if ($reconstructedOrderBy === null) {
124
                $reconstructedOrderBy = implode(', ', $reconstructedOrderBys);
125
            }
126
        }
127
128
        foreach ($additionalTablesFetch as $additionalTable) {
129
            $relatedTables = $this->tdbmService->_getRelatedTablesByInheritance($additionalTable);
130
            $tableGroupName = $this->getTableGroupName($relatedTables);
131
            foreach ($relatedTables as $table) {
132
                $tableGroups[$table] = $tableGroupName;
133
            }
134
            $allFetchedTables = array_merge($allFetchedTables, $relatedTables);
135
        }
136
137
        // Let's remove any duplicate
138
        $allFetchedTables = array_flip(array_flip($allFetchedTables));
139
140
        // Now, let's build the column list
141
        foreach ($allFetchedTables as $table) {
142
            foreach ($this->schema->getTable($table)->getColumns() as $column) {
143
                $columnName = $column->getName();
144
                $columnDescList[] = [
145
                    'as' => $table.'____'.$columnName,
146
                    'table' => $table,
147
                    'column' => $columnName,
148
                    'type' => $column->getType(),
149
                    'tableGroup' => $tableGroups[$table],
150
                ];
151
                $columnsList[] = $connection->quoteIdentifier($table).'.'.$connection->quoteIdentifier($columnName).' as '.
152
                    $connection->quoteIdentifier($table.'____'.$columnName);
153
            }
154
        }
155
156
        return [$columnDescList, $columnsList, $reconstructedOrderBy];
157
    }
158
159
    abstract protected function compute();
160
161
    /**
162
     * Returns an identifier for the group of tables passed in parameter.
163
     *
164
     * @param string[] $relatedTables
165
     *
166
     * @return string
167
     */
168
    protected function getTableGroupName(array $relatedTables)
169
    {
170
        sort($relatedTables);
171
172
        return implode('_``_', $relatedTables);
173
    }
174
175
    public function getMagicSql() : string
176
    {
177
        if ($this->magicSql === null) {
178
            $this->compute();
179
        }
180
181
        return $this->magicSql;
182
    }
183
184
    public function getMagicSqlCount() : string
185
    {
186
        if ($this->magicSqlCount === null) {
187
            $this->compute();
188
        }
189
190
        return $this->magicSqlCount;
191
    }
192
193
    public function getColumnDescriptors() : array
194
    {
195
        if ($this->columnDescList === null) {
196
            $this->compute();
197
        }
198
199
        return $this->columnDescList;
200
    }
201
202
    /**
203
     * Sets the ORDER BY directive executed in SQL.
204
     *
205
     * For instance:
206
     *
207
     *  $queryFactory->sort('label ASC, status DESC');
208
     *
209
     * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
210
     * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
211
     *
212
     *  $queryFactory->sort(new UncheckedOrderBy('RAND()'))
213
     *
214
     * @param string|UncheckedOrderBy|null $orderBy
215
     */
216
    public function sort($orderBy)
217
    {
218
        $this->orderBy = $orderBy;
219
        $this->magicSql = null;
220
        $this->magicSqlCount = null;
221
        $this->columnDescList = null;
222
    }
223
}
224