Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 81 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
| QueryBuilder | |
0.00% |
0 / 81 |
|
0.00% |
0 / 12 |
1332 | |
0.00% |
0 / 1 |
| columnDefinition | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| primaryKeyDefinition | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| foreignKeyDefinition | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| isTableExist | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| listTables | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| describeTable | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| columnsByTableDescription | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| createTable | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
42 | |||
| findAll | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
| findAllCount | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| fieldNames | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
| select | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| joins | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| where | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| groupBy | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| orderBy | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
| limit | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
| currentColumn | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Dynart\Micro\Entities; |
| 4 | |
| 5 | use Dynart\Micro\ConfigInterface; |
| 6 | |
| 7 | abstract class QueryBuilder { |
| 8 | |
| 9 | const CONFIG_MAX_LIMIT = 'entities.query_builder.max_limit'; |
| 10 | const DEFAULT_MAX_LIMIT = 1000; |
| 11 | |
| 12 | const INDENTATION = ' '; |
| 13 | |
| 14 | private static int $subQueryCounter = 0; |
| 15 | |
| 16 | protected string $currentClassNameForException = ''; |
| 17 | protected string $currentColumnNameForException = ''; |
| 18 | protected int $maxLimit; |
| 19 | |
| 20 | abstract public function columnDefinition(string $columnName, array $columnData): string; |
| 21 | abstract public function primaryKeyDefinition(string $className): string; |
| 22 | abstract public function foreignKeyDefinition(string $columnName, array $columnData): string; |
| 23 | abstract public function isTableExist(string $dbNameParam, string $tableNameParam): string; |
| 24 | abstract public function listTables(): string; |
| 25 | abstract public function describeTable(string $className): string; |
| 26 | abstract public function columnsByTableDescription(array $data): array; |
| 27 | |
| 28 | public function __construct( |
| 29 | ConfigInterface $config, |
| 30 | protected Database $db, |
| 31 | protected EntityManager $em, |
| 32 | ) { |
| 33 | $this->maxLimit = $config->get(self::CONFIG_MAX_LIMIT, self::DEFAULT_MAX_LIMIT); |
| 34 | } |
| 35 | |
| 36 | public function createTable(string $className, bool $ifNotExists = false): string { |
| 37 | $this->currentClassNameForException = $className; |
| 38 | $allColumnDef = []; |
| 39 | $allForeignKeyDef = []; |
| 40 | foreach ($this->em->tableColumns($className) as $columnName => $columnData) { |
| 41 | $this->currentColumnNameForException = $columnName; |
| 42 | $allColumnDef[] = self::INDENTATION . $this->columnDefinition($columnName, $columnData); |
| 43 | $foreignKeyDef = $this->foreignKeyDefinition($columnName, $columnData); |
| 44 | if ($foreignKeyDef) { |
| 45 | $allForeignKeyDef[] = self::INDENTATION . $foreignKeyDef; |
| 46 | } |
| 47 | } |
| 48 | $primaryKeyDef = $this->primaryKeyDefinition($className); |
| 49 | $safeTableName = $this->em->safeTableName($className); |
| 50 | $result = "create table "; |
| 51 | if ($ifNotExists) { |
| 52 | $result .= "if not exists "; |
| 53 | } |
| 54 | $result .= "$safeTableName (\n"; |
| 55 | $result .= join(",\n", $allColumnDef); |
| 56 | if ($primaryKeyDef) { |
| 57 | $result .= ",\n" . self::INDENTATION . $primaryKeyDef; |
| 58 | } |
| 59 | if (!empty($allForeignKeyDef)) { |
| 60 | $result .= ",\n" . join(",\n", $allForeignKeyDef); |
| 61 | } |
| 62 | $result .= "\n)"; |
| 63 | return $result; |
| 64 | } |
| 65 | |
| 66 | // TODO: public function findAllUnion(array $queries): string |
| 67 | |
| 68 | public function findAll(Query $query, array $fields = []): string { |
| 69 | $sql = $this->select($query, $fields); |
| 70 | $sql .= $this->joins($query); |
| 71 | $sql .= $this->where($query); |
| 72 | $sql .= $this->groupBy($query); |
| 73 | $sql .= $this->orderBy($query); |
| 74 | $sql .= $this->limit($query); |
| 75 | return $sql; |
| 76 | } |
| 77 | |
| 78 | public function findAllCount(Query $query): string { |
| 79 | $sql = $this->select($query, ['c' => ['count(1)']]); |
| 80 | $sql .= $this->joins($query); |
| 81 | $sql .= $this->where($query); |
| 82 | $sql .= $this->groupBy($query); |
| 83 | return $sql; |
| 84 | } |
| 85 | |
| 86 | public function fieldNames(array $fields): array { |
| 87 | $result = []; |
| 88 | foreach ($fields as $as => $name) { |
| 89 | $safeName = is_array($name) ? $name[0] : $this->db->escapeName($name); |
| 90 | if (is_int($as)) { |
| 91 | $result[] = $safeName; |
| 92 | } else { |
| 93 | $result[] = $safeName.' as '.$this->db->escapeName($as); |
| 94 | } |
| 95 | } |
| 96 | return $result; |
| 97 | } |
| 98 | |
| 99 | protected function select(Query $query, array $fields = []): string { |
| 100 | $selectFields = empty($fields) ? $query->fields() : $fields; |
| 101 | $queryFrom = $query->from(); |
| 102 | if (is_subclass_of($queryFrom, Query::class)) { |
| 103 | self::$subQueryCounter++; // TODO: better solution? |
| 104 | $from = '('.$this->findAll($queryFrom, []).') S'.self::$subQueryCounter; |
| 105 | } else { |
| 106 | $from = $this->em->safeTableName($queryFrom); |
| 107 | } |
| 108 | return 'select '.join(', ', $this->fieldNames($selectFields)).' from '.$from; |
| 109 | } |
| 110 | |
| 111 | protected function joins(Query $query): string { |
| 112 | $joins = []; |
| 113 | foreach ($query->joins() as $join) { |
| 114 | [$type, $from, $condition] = $join; |
| 115 | $fromStr = is_array($from) |
| 116 | ? $this->em->safeTableName($from[0]).' as '.$this->db->escapeName($from[1]) |
| 117 | : $this->em->safeTableName($from); |
| 118 | $joins[] = $type.' join '.$fromStr.' on '.$condition; |
| 119 | } |
| 120 | return $joins ? join("\n", $joins) : ''; |
| 121 | } |
| 122 | |
| 123 | protected function where(Query $query): string { |
| 124 | return empty($query->conditions()) |
| 125 | ? '' |
| 126 | : ' where ('.join(') and (', $query->conditions()).')'; |
| 127 | } |
| 128 | |
| 129 | protected function groupBy(Query $query): string { |
| 130 | return empty($query->groupBy()) ? '' : ' group by '.join(', ', $query->groupBy()); |
| 131 | } |
| 132 | |
| 133 | protected function orderBy(Query $query): string { |
| 134 | $orders = []; |
| 135 | $fieldNames = array_keys($query->fields()); |
| 136 | foreach ($query->orderBy() as $orderBy) { |
| 137 | if (in_array($orderBy[0], $fieldNames)) { |
| 138 | $orders[] = $this->db->escapeName($orderBy[0]).' '.($orderBy[1] == 'desc' ? 'desc' : 'asc'); |
| 139 | } |
| 140 | } |
| 141 | return $orders ? ' order by '.join(', ', $orders) : ''; |
| 142 | } |
| 143 | |
| 144 | protected function limit(Query $query): string { |
| 145 | if ($query->offset() == -1 || $query->max() == -1) { |
| 146 | return ''; |
| 147 | } |
| 148 | $offset = $query->offset(); |
| 149 | $max = $query->max(); |
| 150 | if ($offset < 0) { |
| 151 | $offset = 0; |
| 152 | } |
| 153 | if ($max < 1) { |
| 154 | $max = 1; |
| 155 | } |
| 156 | if ($max > $this->maxLimit) { |
| 157 | $max = $this->maxLimit; |
| 158 | } |
| 159 | return ' limit '.$offset.', '.$max; |
| 160 | } |
| 161 | |
| 162 | protected function currentColumn(): string { |
| 163 | // TODO: better solution? |
| 164 | return $this->currentClassNameForException.'::'.$this->currentColumnNameForException; |
| 165 | } |
| 166 | } |