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