thecodingmachine /
tdbm
| 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
Unused Code
introduced
by
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
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
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
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 |