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