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