Issues (269)

QueryFactory/FindObjectsFromSqlQueryFactory.php (3 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace TheCodingMachine\TDBM\QueryFactory;
6
7
use Doctrine\Common\Cache\Cache;
8
use Doctrine\DBAL\Platforms\MySQLPlatform;
9
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10
use Doctrine\DBAL\Schema\Schema;
11
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
12
use TheCodingMachine\TDBM\OrderByAnalyzer;
13
use TheCodingMachine\TDBM\TDBMException;
14
use TheCodingMachine\TDBM\TDBMService;
15
16
use function implode;
17
18
/**
19
 * This class is in charge of creating the MagicQuery SQL based on parameters passed to findObjectsFromSql method.
20
 */
21
class FindObjectsFromSqlQueryFactory extends AbstractQueryFactory
22
{
23
    private $from;
24
    private $filterString;
25
    private $cache;
26
    private $cachePrefix;
27
    private $schemaAnalyzer;
28
29
    public function __construct(string $mainTable, string $from, string $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, SchemaAnalyzer $schemaAnalyzer, Cache $cache, string $cachePrefix)
30
    {
31
        parent::__construct($tdbmService, $schema, $orderByAnalyzer, $mainTable, $orderBy);
32
        $this->from = $from;
33
        $this->filterString = $filterString;
34
        $this->schemaAnalyzer = $schemaAnalyzer;
35
        $this->cache = $cache;
36
        $this->cachePrefix = $cachePrefix;
37
    }
38
39
    protected function compute(): void
40
    {
41
        // We quote in MySQL because of MagicQuery that will be applied.
42
        $mySqlPlatform = new MySqlPlatform();
43
44
        $columnsList = null;
0 ignored issues
show
The assignment to $columnsList is dead and can be removed.
Loading history...
45
46
        $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($this->mainTable);
47
48
        list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy, false);
49
50
        $sql = 'SELECT DISTINCT '.implode(', ', $columnsList).' FROM '.$this->from;
0 ignored issues
show
$columnsList of type null is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

50
        $sql = 'SELECT DISTINCT '.implode(', ', /** @scrutinizer ignore-type */ $columnsList).' FROM '.$this->from;
Loading history...
51
52
        // Let's compute the COUNT.
53
        $pkColumnNames = $this->tdbmService->getPrimaryKeyColumns($this->mainTable);
54
        $pkColumnNames = array_map(function ($pkColumn) use ($mySqlPlatform) {
55
            return $mySqlPlatform->quoteIdentifier($this->mainTable).'.'.$mySqlPlatform->quoteIdentifier($pkColumn);
56
        }, $pkColumnNames);
57
58
        $countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM '.$this->from;
59
        $subQuery = 'SELECT DISTINCT '.implode(', ', $pkColumnNames).' FROM '.$this->from;
60
61
        // Add joins on inherited tables if necessary
62
        if (count($allFetchedTables) > 1) {
63
            $joinSql = '';
64
            $parentFks = $this->getParentRelationshipForeignKeys($this->mainTable);
65
            foreach ($parentFks as $fk) {
66
                $joinSql .= sprintf(
67
                    ' JOIN %s ON (%s.%s = %s.%s)',
68
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
69
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
70
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedLocalColumns()[0]),
71
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
72
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedForeignColumns()[0])
73
                );
74
            }
75
76
            $childrenFks = $this->getChildrenRelationshipForeignKeys($this->mainTable);
77
            foreach ($childrenFks as $fk) {
78
                $joinSql .= sprintf(
79
                    ' LEFT JOIN %s ON (%s.%s = %s.%s)',
80
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
81
                    $mySqlPlatform->quoteIdentifier($fk->getForeignTableName()),
82
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedForeignColumns()[0]),
83
                    $mySqlPlatform->quoteIdentifier($fk->getLocalTableName()),
84
                    $mySqlPlatform->quoteIdentifier($fk->getUnquotedLocalColumns()[0])
85
                );
86
            }
87
88
            $sql .= $joinSql;
89
        }
90
91
        if (!empty($this->filterString)) {
92
            $sql .= ' WHERE '.$this->filterString;
93
            $countSql .= ' WHERE '.$this->filterString;
94
            $subQuery .= ' WHERE '.$this->filterString;
95
        }
96
97
        if (!empty($orderString)) {
98
            $sql .= ' ORDER BY '.$orderString;
99
        }
100
101
        if (stripos($countSql, 'GROUP BY') !== false) {
102
            throw new TDBMException('Unsupported use of GROUP BY in SQL request.');
103
        }
104
105
        $this->magicSql = $sql;
106
        $this->magicSqlCount = $countSql;
107
        $this->magicSqlSubQuery = $subQuery;
108
        $this->columnDescList = $columnDescList;
109
    }
110
111
    /**
112
     * @param string $tableName
113
     *
114
     * @return ForeignKeyConstraint[]
115
     */
116
    private function getParentRelationshipForeignKeys(string $tableName): array
117
    {
118
        return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function () use ($tableName) {
119
            return $this->getParentRelationshipForeignKeysWithoutCache($tableName);
120
        });
121
    }
122
123
    /**
124
     * @param string $tableName
125
     *
126
     * @return ForeignKeyConstraint[]
127
     */
128
    private function getParentRelationshipForeignKeysWithoutCache(string $tableName): array
129
    {
130
        $parentFks = [];
131
        $currentTable = $tableName;
132
        while ($currentFk = $this->schemaAnalyzer->getParentRelationship($currentTable)) {
133
            $currentTable = $currentFk->getForeignTableName();
134
            $parentFks[] = $currentFk;
135
        }
136
137
        return $parentFks;
138
    }
139
140
    /**
141
     * @param string $tableName
142
     *
143
     * @return ForeignKeyConstraint[]
144
     */
145
    private function getChildrenRelationshipForeignKeys(string $tableName): array
146
    {
147
        return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function () use ($tableName) {
148
            return $this->getChildrenRelationshipForeignKeysWithoutCache($tableName);
149
        });
150
    }
151
152
    /**
153
     * @param string $tableName
154
     *
155
     * @return ForeignKeyConstraint[]
156
     */
157
    private function getChildrenRelationshipForeignKeysWithoutCache(string $tableName): array
158
    {
159
        $children = $this->schemaAnalyzer->getChildrenRelationships($tableName);
160
161
        if (!empty($children)) {
162
            $fksTables = array_map(function (ForeignKeyConstraint $fk) {
163
                return $this->getChildrenRelationshipForeignKeys($fk->getLocalTableName());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...nt::getLocalTableName() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

163
                return $this->getChildrenRelationshipForeignKeys(/** @scrutinizer ignore-deprecated */ $fk->getLocalTableName());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
164
            }, $children);
165
166
            $fks = array_merge($children, ...$fksTables);
167
168
            return $fks;
169
        } else {
170
            return [];
171
        }
172
    }
173
174
    /**
175
     * Returns an item from cache or computes it using $closure and puts it in cache.
176
     *
177
     * @param string   $key
178
     * @param callable $closure
179
     *
180
     * @return mixed
181
     */
182
    protected function fromCache(string $key, callable $closure)
183
    {
184
        $item = $this->cache->fetch($key);
185
        if ($item === false) {
186
            $item = $closure();
187
            $this->cache->save($key, $item);
188
        }
189
190
        return $item;
191
    }
192
}
193