1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Xmf\Database;
13:
14: use Xmf\Module\Helper;
15: use Xmf\Yaml;
16:
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
34: class Migrate
35: {
36:
37:
38: protected $helper;
39:
40:
41: protected $moduleTables;
42:
43:
44: protected $tableHandler;
45:
46:
47: protected $tableDefinitionFile;
48:
49:
50: protected $targetDefinitions;
51:
52: 53: 54: 55: 56: 57: 58: 59:
60: public function __construct($dirname)
61: {
62: $this->helper = Helper::getHelper($dirname);
63: if (false === $this->helper) {
64: throw new \InvalidArgumentException("Invalid module $dirname specified");
65: }
66: $module = $this->helper->getModule();
67: $this->moduleTables = $module->getInfo('tables');
68: if (empty($this->moduleTables)) {
69: throw new \RuntimeException("No tables established in module");
70: }
71: $version = $module->getInfo('version');
72: $this->tableDefinitionFile = $this->helper->path("sql/{$dirname}_{$version}_migrate.yml");
73: $this->tableHandler = new Tables();
74: }
75:
76: 77: 78: 79: 80: 81: 82: 83: 84:
85: public function saveCurrentSchema()
86: {
87: $this->tableHandler = new Tables();
88:
89: $schema = $this->getCurrentSchema();
90:
91: foreach ($schema as $tableName => $tableData) {
92: unset($schema[$tableName]['name']);
93: }
94:
95: return Yaml::save($schema, $this->tableDefinitionFile);
96: }
97:
98: 99: 100: 101: 102:
103: public function getCurrentSchema()
104: {
105: foreach ($this->moduleTables as $tableName) {
106: $this->tableHandler->useTable($tableName);
107: }
108:
109: return $this->tableHandler->dumpTables();
110: }
111:
112: 113: 114: 115: 116: 117: 118:
119: public function getTargetDefinitions()
120: {
121: if (!isset($this->targetDefinitions)) {
122: $this->targetDefinitions = Yaml::read($this->tableDefinitionFile);
123: if (null === $this->targetDefinitions) {
124: throw new \RuntimeException("No schema definition " . $this->tableDefinitionFile);
125: }
126: }
127: return $this->targetDefinitions;
128: }
129:
130: 131: 132: 133: 134: 135: 136:
137: public function synchronizeSchema($force = true)
138: {
139: $this->tableHandler = new Tables();
140: $this->getSynchronizeDDL();
141: return $this->tableHandler->executeQueue($force);
142: }
143:
144: 145: 146: 147: 148:
149: public function getSynchronizeDDL()
150: {
151: $this->getTargetDefinitions();
152: $this->preSyncActions();
153: foreach ($this->moduleTables as $tableName) {
154: if ($this->tableHandler->useTable($tableName)) {
155: $this->synchronizeTable($tableName);
156: } else {
157: $this->addMissingTable($tableName);
158: }
159: }
160: return $this->tableHandler->dumpQueue();
161: }
162:
163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179:
180: protected function preSyncActions()
181: {
182: }
183:
184: 185: 186: 187: 188: 189: 190:
191: protected function addMissingTable($tableName)
192: {
193: $this->tableHandler->addTable($tableName);
194: $this->tableHandler->setTableOptions($tableName, $this->targetDefinitions[$tableName]['options']);
195: foreach ($this->targetDefinitions[$tableName]['columns'] as $column) {
196: $this->tableHandler->addColumn($tableName, $column['name'], $column['attributes']);
197: }
198: foreach ($this->targetDefinitions[$tableName]['keys'] as $key => $keyData) {
199: if ($key === 'PRIMARY') {
200: $this->tableHandler->addPrimaryKey($tableName, $keyData['columns']);
201: } else {
202: $this->tableHandler->addIndex($key, $tableName, $keyData['columns'], $keyData['unique']);
203: }
204: }
205: }
206:
207: 208: 209: 210: 211: 212: 213:
214: protected function synchronizeTable($tableName)
215: {
216: foreach ($this->targetDefinitions[$tableName]['columns'] as $column) {
217: $attributes = $this->tableHandler->getColumnAttributes($tableName, $column['name']);
218: if ($attributes === false) {
219: $this->tableHandler->addColumn($tableName, $column['name'], $column['attributes']);
220: } elseif ($column['attributes'] !== $attributes) {
221: $this->tableHandler->alterColumn($tableName, $column['name'], $column['attributes']);
222: }
223: }
224:
225: $tableDef = $this->tableHandler->dumpTables();
226: if (isset($tableDef[$tableName])) {
227: foreach ($tableDef[$tableName]['columns'] as $columnData) {
228: if (!$this->targetHasColumn($tableName, $columnData['name'])) {
229: $this->tableHandler->dropColumn($tableName, $columnData['name']);
230: }
231: }
232: }
233:
234: $existingIndexes = $this->tableHandler->getTableIndexes($tableName);
235: if (isset($this->targetDefinitions[$tableName]['keys'])) {
236: foreach ($this->targetDefinitions[$tableName]['keys'] as $key => $keyData) {
237: if ($key === 'PRIMARY') {
238: if (!isset($existingIndexes[$key])) {
239: $this->tableHandler->addPrimaryKey($tableName, $keyData['columns']);
240: } elseif ($existingIndexes[$key]['columns'] !== $keyData['columns']) {
241: $this->tableHandler->dropPrimaryKey($tableName);
242: $this->tableHandler->addPrimaryKey($tableName, $keyData['columns']);
243: }
244: } else {
245: if (!isset($existingIndexes[$key])) {
246: $this->tableHandler->addIndex($key, $tableName, $keyData['columns'], $keyData['unique']);
247: } elseif ($existingIndexes[$key]['unique'] !== $keyData['unique']
248: || $existingIndexes[$key]['columns'] !== $keyData['columns']
249: ) {
250: $this->tableHandler->dropIndex($key, $tableName);
251: $this->tableHandler->addIndex($key, $tableName, $keyData['columns'], $keyData['unique']);
252: }
253: }
254: }
255: }
256: if (false !== $existingIndexes) {
257: foreach ($existingIndexes as $key => $keyData) {
258: if (!isset($this->targetDefinitions[$tableName]['keys'][$key])) {
259: $this->tableHandler->dropIndex($key, $tableName);
260: }
261: }
262: }
263: }
264:
265: 266: 267: 268: 269: 270: 271: 272:
273: protected function targetHasColumn($tableName, $columnName)
274: {
275: if (isset($this->targetDefinitions[$tableName])) {
276: foreach ($this->targetDefinitions[$tableName]['columns'] as $col) {
277: if (strcasecmp($col['name'], $columnName) === 0) {
278: return true;
279: }
280: }
281: }
282:
283: return false;
284: }
285:
286: 287: 288: 289: 290: 291: 292:
293: protected function targetHasTable($tableName)
294: {
295: if (isset($this->targetDefinitions[$tableName])) {
296: return true;
297: }
298: return false;
299: }
300:
301: 302: 303: 304: 305:
306: public function getLastError()
307: {
308: return $this->tableHandler->getLastError();
309: }
310:
311: 312: 313: 314: 315:
316: public function getLastErrNo()
317: {
318: return $this->tableHandler->getLastErrNo();
319: }
320: }
321: