1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class Structure implements IStructure
17: {
18: use Nette\SmartObject;
19:
20:
21: protected $connection;
22:
23:
24: protected $cache;
25:
26:
27: protected $structure;
28:
29:
30: protected $isRebuilt = false;
31:
32:
33: public function __construct(Connection $connection, Nette\Caching\IStorage $cacheStorage)
34: {
35: $this->connection = $connection;
36: $this->cache = new Nette\Caching\Cache($cacheStorage, 'Nette.Database.Structure.' . md5($this->connection->getDsn()));
37: }
38:
39:
40: public function getTables()
41: {
42: $this->needStructure();
43: return $this->structure['tables'];
44: }
45:
46:
47: public function getColumns($table)
48: {
49: $this->needStructure();
50: $table = $this->resolveFQTableName($table);
51:
52: return $this->structure['columns'][$table];
53: }
54:
55:
56: public function getPrimaryKey($table)
57: {
58: $this->needStructure();
59: $table = $this->resolveFQTableName($table);
60:
61: if (!isset($this->structure['primary'][$table])) {
62: return null;
63: }
64:
65: return $this->structure['primary'][$table];
66: }
67:
68:
69: public function getPrimaryAutoincrementKey($table)
70: {
71: $primaryKey = $this->getPrimaryKey($table);
72: if (!$primaryKey) {
73: return null;
74: }
75:
76:
77: if (is_array($primaryKey)) {
78: $keys = array_flip($primaryKey);
79: foreach ($this->getColumns($table) as $column) {
80: if (isset($keys[$column['name']]) && $column['autoincrement']) {
81: return $column['name'];
82: }
83: }
84: return null;
85: }
86:
87:
88: foreach ($this->getColumns($table) as $column) {
89: if ($column['name'] == $primaryKey) {
90: return $column['autoincrement'] ? $column['name'] : null;
91: }
92: }
93:
94: return null;
95: }
96:
97:
98: public function getPrimaryKeySequence($table)
99: {
100: $this->needStructure();
101: $table = $this->resolveFQTableName($table);
102:
103: if (!$this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
104: return null;
105: }
106:
107: $autoincrementPrimaryKeyName = $this->getPrimaryAutoincrementKey($table);
108: if (!$autoincrementPrimaryKeyName) {
109: return null;
110: }
111:
112:
113: foreach ($this->structure['columns'][$table] as $columnMeta) {
114: if ($columnMeta['name'] === $autoincrementPrimaryKeyName) {
115: return isset($columnMeta['vendor']['sequence']) ? $columnMeta['vendor']['sequence'] : null;
116: }
117: }
118:
119: return null;
120: }
121:
122:
123: public function getHasManyReference($table, $targetTable = null)
124: {
125: $this->needStructure();
126: $table = $this->resolveFQTableName($table);
127:
128: if ($targetTable) {
129: $targetTable = $this->resolveFQTableName($targetTable);
130: foreach ($this->structure['hasMany'][$table] as $key => $value) {
131: if (strtolower($key) === $targetTable) {
132: return $this->structure['hasMany'][$table][$key];
133: }
134: }
135:
136: return null;
137:
138: } else {
139: if (!isset($this->structure['hasMany'][$table])) {
140: return [];
141: }
142: return $this->structure['hasMany'][$table];
143: }
144: }
145:
146:
147: public function getBelongsToReference($table, $column = null)
148: {
149: $this->needStructure();
150: $table = $this->resolveFQTableName($table);
151:
152: if ($column) {
153: $column = strtolower($column);
154: if (!isset($this->structure['belongsTo'][$table][$column])) {
155: return null;
156: }
157: return $this->structure['belongsTo'][$table][$column];
158:
159: } else {
160: if (!isset($this->structure['belongsTo'][$table])) {
161: return [];
162: }
163: return $this->structure['belongsTo'][$table];
164: }
165: }
166:
167:
168: public function rebuild()
169: {
170: $this->structure = $this->loadStructure();
171: $this->cache->save('structure', $this->structure);
172: }
173:
174:
175: public function isRebuilt()
176: {
177: return $this->isRebuilt;
178: }
179:
180:
181: protected function needStructure()
182: {
183: if ($this->structure !== null) {
184: return;
185: }
186:
187: $this->structure = $this->cache->load('structure', [$this, 'loadStructure']);
188: }
189:
190:
191: 192: 193:
194: public function loadStructure()
195: {
196: $driver = $this->connection->getSupplementalDriver();
197:
198: $structure = [];
199: $structure['tables'] = $driver->getTables();
200:
201: foreach ($structure['tables'] as $tablePair) {
202: if (isset($tablePair['fullName'])) {
203: $table = $tablePair['fullName'];
204: $structure['aliases'][strtolower($tablePair['name'])] = strtolower($table);
205: } else {
206: $table = $tablePair['name'];
207: }
208:
209: $structure['columns'][strtolower($table)] = $columns = $driver->getColumns($table);
210:
211: if (!$tablePair['view']) {
212: $structure['primary'][strtolower($table)] = $this->analyzePrimaryKey($columns);
213: $this->analyzeForeignKeys($structure, $table);
214: }
215: }
216:
217: if (isset($structure['hasMany'])) {
218: foreach ($structure['hasMany'] as &$table) {
219: uksort($table, function ($a, $b) {
220: return strlen($a) - strlen($b);
221: });
222: }
223: }
224:
225: $this->isRebuilt = true;
226:
227: return $structure;
228: }
229:
230:
231: protected function analyzePrimaryKey(array $columns)
232: {
233: $primary = [];
234: foreach ($columns as $column) {
235: if ($column['primary']) {
236: $primary[] = $column['name'];
237: }
238: }
239:
240: if (count($primary) === 0) {
241: return null;
242: } elseif (count($primary) === 1) {
243: return reset($primary);
244: } else {
245: return $primary;
246: }
247: }
248:
249:
250: protected function analyzeForeignKeys(&$structure, $table)
251: {
252: $lowerTable = strtolower($table);
253: foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) {
254: $structure['belongsTo'][$lowerTable][$row['local']] = $row['table'];
255: $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local'];
256: }
257:
258: if (isset($structure['belongsTo'][$lowerTable])) {
259: uksort($structure['belongsTo'][$lowerTable], function ($a, $b) {
260: return strlen($a) - strlen($b);
261: });
262: }
263: }
264:
265:
266: protected function resolveFQTableName($table)
267: {
268: $name = strtolower($table);
269: if (isset($this->structure['columns'][$name])) {
270: return $name;
271: }
272:
273: if (isset($this->structure['aliases'][$name])) {
274: return $this->structure['aliases'][$name];
275: }
276:
277: if (!$this->isRebuilt()) {
278: $this->rebuild();
279: return $this->resolveFQTableName($table);
280: }
281:
282: throw new Nette\InvalidArgumentException("Table '$name' does not exist.");
283: }
284: }
285: