Passed
Pull Request — master (#68)
by David
03:59 queued 44s
created

FindObjectsFromSqlQueryFactory::compute()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 67
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 67
rs 7.0237
c 0
b 0
f 0
cc 7
eloc 41
nc 16
nop 0

How to fix   Long Method   

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 TheCodingMachine\TDBM\QueryFactory;
4
5
use Doctrine\Common\Cache\Cache;
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Schema;
9
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
10
use TheCodingMachine\TDBM\OrderByAnalyzer;
11
use TheCodingMachine\TDBM\TDBMException;
12
use TheCodingMachine\TDBM\TDBMService;
13
14
/**
15
 * This class is in charge of creating the MagicQuery SQL based on parameters passed to findObjectsFromSql method.
16
 */
17
class FindObjectsFromSqlQueryFactory extends AbstractQueryFactory
18
{
19
    private $mainTable;
20
    private $from;
21
    private $filterString;
22
    private $cache;
23
    private $cachePrefix;
24
25
    public function __construct(string $mainTable, string $from, $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, SchemaAnalyzer $schemaAnalyzer, Cache $cache, string $cachePrefix)
26
    {
27
        parent::__construct($tdbmService, $schema, $orderByAnalyzer, $orderBy);
28
        $this->mainTable = $mainTable;
29
        $this->from = $from;
30
        $this->filterString = $filterString;
31
        $this->schemaAnalyzer = $schemaAnalyzer;
0 ignored issues
show
Bug Best Practice introduced by
The property schemaAnalyzer does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
32
        $this->cache = $cache;
33
        $this->cachePrefix = $cachePrefix;
34
    }
35
36
    protected function compute()
37
    {
38
        // We quote in MySQL because of MagicQuery that will be applied.
39
        $mySqlPlatform = new MySqlPlatform();
40
41
        $columnsList = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $columnsList is dead and can be removed.
Loading history...
42
43
        $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($this->mainTable);
44
45
        list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy, false);
46
47
        $sql = 'SELECT DISTINCT '.implode(', ', $columnsList).' FROM '.$this->from;
48
49
        // Let's compute the COUNT.
50
        $pkColumnNames = $this->tdbmService->getPrimaryKeyColumns($this->mainTable);
51
        $pkColumnNames = array_map(function ($pkColumn) use ($mySqlPlatform) {
52
            return $mySqlPlatform->quoteIdentifier($this->mainTable).'.'.$mySqlPlatform->quoteIdentifier($pkColumn);
53
        }, $pkColumnNames);
54
55
        $countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM '.$this->from;
56
57
        // Add joins on inherited tables if necessary
58
        if (count($allFetchedTables) > 1) {
59
            $joinSql = '';
60
            $parentFks = $this->getParentRelationshipForeignKeys($this->mainTable);
61
            foreach ($parentFks as $fk) {
62
                $joinSql .= sprintf(
63
                    ' JOIN %s ON (%s.%s = %s.%s)',
64
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
65
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
66
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedLocalColumns()[0]),
67
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
68
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedForeignColumns()[0])
69
                );
70
            }
71
72
            $childrenFks = $this->getChildrenRelationshipForeignKeys($this->mainTable);
73
            foreach ($childrenFks as $fk) {
74
                $joinSql .= sprintf(
75
                    ' LEFT JOIN %s ON (%s.%s = %s.%s)',
76
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
77
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
78
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedForeignColumns()[0]),
79
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
80
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedLocalColumns()[0])
81
                );
82
            }
83
84
            $sql .= $joinSql;
85
        }
86
87
        if (!empty($this->filterString)) {
88
            $sql .= ' WHERE '.$this->filterString;
89
            $countSql .= ' WHERE '.$this->filterString;
90
        }
91
92
        if (!empty($orderString)) {
93
            $sql .= ' ORDER BY '.$orderString;
94
        }
95
96
        if (stripos($countSql, 'GROUP BY') !== false) {
97
            throw new TDBMException('Unsupported use of GROUP BY in SQL request.');
98
        }
99
100
        $this->magicSql = $sql;
101
        $this->magicSqlCount = $countSql;
102
        $this->columnDescList = $columnDescList;
103
    }
104
105
    /**
106
     * @param string $tableName
107
     *
108
     * @return ForeignKeyConstraint[]
109
     */
110
    private function getParentRelationshipForeignKeys($tableName)
111
    {
112
        return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function () use ($tableName) {
113
            return $this->getParentRelationshipForeignKeysWithoutCache($tableName);
114
        });
115
    }
116
117
    /**
118
     * @param string $tableName
119
     *
120
     * @return ForeignKeyConstraint[]
121
     */
122
    private function getParentRelationshipForeignKeysWithoutCache($tableName)
123
    {
124
        $parentFks = [];
125
        $currentTable = $tableName;
126
        while ($currentFk = $this->schemaAnalyzer->getParentRelationship($currentTable)) {
127
            $currentTable = $currentFk->getForeignTableName();
128
            $parentFks[] = $currentFk;
129
        }
130
131
        return $parentFks;
132
    }
133
134
    /**
135
     * @param string $tableName
136
     *
137
     * @return ForeignKeyConstraint[]
138
     */
139
    private function getChildrenRelationshipForeignKeys(string $tableName) : array
140
    {
141
        return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function () use ($tableName) {
142
            return $this->getChildrenRelationshipForeignKeysWithoutCache($tableName);
143
        });
144
    }
145
146
    /**
147
     * @param string $tableName
148
     *
149
     * @return ForeignKeyConstraint[]
150
     */
151
    private function getChildrenRelationshipForeignKeysWithoutCache(string $tableName) : array
152
    {
153
        $children = $this->schemaAnalyzer->getChildrenRelationships($tableName);
154
155
        if (!empty($children)) {
156
            $fksTables = array_map(function (ForeignKeyConstraint $fk) {
157
                return $this->getChildrenRelationshipForeignKeys($fk->getLocalTableName());
158
            }, $children);
159
160
            $fks = array_merge($children, call_user_func_array('array_merge', $fksTables));
161
162
            return $fks;
163
        } else {
164
            return [];
165
        }
166
    }
167
168
    /**
169
     * Returns an item from cache or computes it using $closure and puts it in cache.
170
     *
171
     * @param string   $key
172
     * @param callable $closure
173
     *
174
     * @return mixed
175
     */
176
    protected function fromCache(string $key, callable $closure)
177
    {
178
        $item = $this->cache->fetch($key);
179
        if ($item === false) {
180
            $item = $closure();
181
            $this->cache->save($key, $item);
182
        }
183
184
        return $item;
185
    }
186
}
187