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 getPrimaryKeySequence($table)
70: {
71: $this->needStructure();
72: $table = $this->resolveFQTableName($table);
73:
74: if (!$this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
75: return NULL;
76: }
77:
78: $primary = $this->getPrimaryKey($table);
79: if (!$primary || is_array($primary)) {
80: return NULL;
81: }
82:
83: foreach ($this->structure['columns'][$table] as $columnMeta) {
84: if ($columnMeta['name'] === $primary) {
85: return isset($columnMeta['vendor']['sequence']) ? $columnMeta['vendor']['sequence'] : NULL;
86: }
87: }
88:
89: return NULL;
90: }
91:
92:
93: public function getHasManyReference($table, $targetTable = NULL)
94: {
95: $this->needStructure();
96: $table = $this->resolveFQTableName($table);
97:
98: if ($targetTable) {
99: $targetTable = $this->resolveFQTableName($targetTable);
100: foreach ($this->structure['hasMany'][$table] as $key => $value) {
101: if (strtolower($key) === $targetTable) {
102: return $this->structure['hasMany'][$table][$key];
103: }
104: }
105:
106: return NULL;
107:
108: } else {
109: if (!isset($this->structure['hasMany'][$table])) {
110: return [];
111: }
112: return $this->structure['hasMany'][$table];
113: }
114: }
115:
116:
117: public function getBelongsToReference($table, $column = NULL)
118: {
119: $this->needStructure();
120: $table = $this->resolveFQTableName($table);
121:
122: if ($column) {
123: $column = strtolower($column);
124: if (!isset($this->structure['belongsTo'][$table][$column])) {
125: return NULL;
126: }
127: return $this->structure['belongsTo'][$table][$column];
128:
129: } else {
130: if (!isset($this->structure['belongsTo'][$table])) {
131: return [];
132: }
133: return $this->structure['belongsTo'][$table];
134: }
135: }
136:
137:
138: public function rebuild()
139: {
140: $this->structure = $this->loadStructure();
141: $this->cache->save('structure', $this->structure);
142: }
143:
144:
145: public function isRebuilt()
146: {
147: return $this->isRebuilt;
148: }
149:
150:
151: protected function needStructure()
152: {
153: if ($this->structure !== NULL) {
154: return;
155: }
156:
157: $this->structure = $this->cache->load('structure', [$this, 'loadStructure']);
158: }
159:
160:
161: 162: 163:
164: public function loadStructure()
165: {
166: $driver = $this->connection->getSupplementalDriver();
167:
168: $structure = [];
169: $structure['tables'] = $driver->getTables();
170:
171: foreach ($structure['tables'] as $tablePair) {
172: if (isset($tablePair['fullName'])) {
173: $table = $tablePair['fullName'];
174: $structure['aliases'][strtolower($tablePair['name'])] = strtolower($table);
175: } else {
176: $table = $tablePair['name'];
177: }
178:
179: $structure['columns'][strtolower($table)] = $columns = $driver->getColumns($table);
180:
181: if (!$tablePair['view']) {
182: $structure['primary'][strtolower($table)] = $this->analyzePrimaryKey($columns);
183: $this->analyzeForeignKeys($structure, $table);
184: }
185: }
186:
187: if (isset($structure['hasMany'])) {
188: foreach ($structure['hasMany'] as & $table) {
189: uksort($table, function ($a, $b) {
190: return strlen($a) - strlen($b);
191: });
192: }
193: }
194:
195: $this->isRebuilt = TRUE;
196:
197: return $structure;
198: }
199:
200:
201: protected function analyzePrimaryKey(array $columns)
202: {
203: $primary = [];
204: foreach ($columns as $column) {
205: if ($column['primary']) {
206: $primary[] = $column['name'];
207: }
208: }
209:
210: if (count($primary) === 0) {
211: return NULL;
212: } elseif (count($primary) === 1) {
213: return reset($primary);
214: } else {
215: return $primary;
216: }
217: }
218:
219:
220: protected function analyzeForeignKeys(& $structure, $table)
221: {
222: $lowerTable = strtolower($table);
223: foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) {
224: $structure['belongsTo'][$lowerTable][$row['local']] = $row['table'];
225: $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local'];
226: }
227:
228: if (isset($structure['belongsTo'][$lowerTable])) {
229: uksort($structure['belongsTo'][$lowerTable], function ($a, $b) {
230: return strlen($a) - strlen($b);
231: });
232: }
233: }
234:
235:
236: protected function resolveFQTableName($table)
237: {
238: $name = strtolower($table);
239: if (isset($this->structure['columns'][$name])) {
240: return $name;
241: }
242:
243: if (isset($this->structure['aliases'][$name])) {
244: return $this->structure['aliases'][$name];
245: }
246:
247: if (!$this->isRebuilt()) {
248: $this->rebuild();
249: return $this->resolveFQTableName($table);
250: }
251:
252: throw new Nette\InvalidArgumentException("Table '$name' does not exist.");
253: }
254:
255: }
256: