PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.6.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.6.0
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Database / Migration / Blueprint.php

Blueprint.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.6.0, at vendor/wpfluent/framework/src/WPFluent/Database/Migration/Blueprint.php

616 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Database\Migration;
4
5 use FluentCommunity\Framework\Database\Schema;
6
7 class Blueprint
8 {
9 protected $table;
10 protected $mode;
11
12 /**
13 * @var ColumnDefinition[]
14 */
15 protected $columns = [];
16
17 /**
18 * Index definitions for CREATE mode.
19 * Each: ['type' => 'KEY|UNIQUE KEY', 'columns' => [...], 'name' => '...']
20 */
21 protected $indexes = [];
22
23 /**
24 * Alter operations (executed in order).
25 * Each: ['type' => string, 'data' => mixed]
26 */
27 protected $operations = [];
28
29 public function __construct($table, $mode = 'create')
30 {
31 $this->table = $table;
32 $this->mode = $mode;
33 }
34
35 // ══════════════════════════════════════════════════════════
36 // CREATE MODE — Column Definitions
37 // ══════════════════════════════════════════════════════════
38
39 public function id($name = 'id')
40 {
41 $col = new ColumnDefinition($name);
42 $col->id();
43 $this->columns[] = $col;
44 return $col;
45 }
46
47 public function string($name, $length = 255)
48 {
49 $col = new ColumnDefinition($name);
50 $col->string($length);
51 $this->columns[] = $col;
52 return $col;
53 }
54
55 public function char($name, $length = 1)
56 {
57 $col = new ColumnDefinition($name);
58 $col->char($length);
59 $this->columns[] = $col;
60 return $col;
61 }
62
63 public function text($name)
64 {
65 $col = new ColumnDefinition($name);
66 $col->text();
67 $this->columns[] = $col;
68 return $col;
69 }
70
71 public function mediumText($name)
72 {
73 $col = new ColumnDefinition($name);
74 $col->mediumText();
75 $this->columns[] = $col;
76 return $col;
77 }
78
79 public function longText($name)
80 {
81 $col = new ColumnDefinition($name);
82 $col->longText();
83 $this->columns[] = $col;
84 return $col;
85 }
86
87 public function integer($name)
88 {
89 $col = new ColumnDefinition($name);
90 $col->integer();
91 $this->columns[] = $col;
92 return $col;
93 }
94
95 public function bigInteger($name)
96 {
97 $col = new ColumnDefinition($name);
98 $col->bigInteger();
99 $this->columns[] = $col;
100 return $col;
101 }
102
103 public function tinyInteger($name)
104 {
105 $col = new ColumnDefinition($name);
106 $col->tinyInteger();
107 $this->columns[] = $col;
108 return $col;
109 }
110
111 public function smallInteger($name)
112 {
113 $col = new ColumnDefinition($name);
114 $col->smallInteger();
115 $this->columns[] = $col;
116 return $col;
117 }
118
119 public function mediumInteger($name)
120 {
121 $col = new ColumnDefinition($name);
122 $col->mediumInteger();
123 $this->columns[] = $col;
124 return $col;
125 }
126
127 public function decimal($name, $precision = 8, $scale = 2)
128 {
129 $col = new ColumnDefinition($name);
130 $col->decimal($precision, $scale);
131 $this->columns[] = $col;
132 return $col;
133 }
134
135 public function float($name)
136 {
137 $col = new ColumnDefinition($name);
138 $col->float();
139 $this->columns[] = $col;
140 return $col;
141 }
142
143 public function double($name)
144 {
145 $col = new ColumnDefinition($name);
146 $col->double();
147 $this->columns[] = $col;
148 return $col;
149 }
150
151 public function boolean($name)
152 {
153 $col = new ColumnDefinition($name);
154 $col->boolean();
155 $this->columns[] = $col;
156 return $col;
157 }
158
159 public function json($name)
160 {
161 $col = new ColumnDefinition($name);
162 $col->json();
163 $this->columns[] = $col;
164 return $col;
165 }
166
167 public function binary($name, $length = 255)
168 {
169 $col = new ColumnDefinition($name);
170 $col->binary($length);
171 $this->columns[] = $col;
172 return $col;
173 }
174
175 public function blob($name)
176 {
177 $col = new ColumnDefinition($name);
178 $col->blob();
179 $this->columns[] = $col;
180 return $col;
181 }
182
183 public function mediumBlob($name)
184 {
185 $col = new ColumnDefinition($name);
186 $col->mediumBlob();
187 $this->columns[] = $col;
188 return $col;
189 }
190
191 public function longBlob($name)
192 {
193 $col = new ColumnDefinition($name);
194 $col->longBlob();
195 $this->columns[] = $col;
196 return $col;
197 }
198
199 public function enum($name, array $values)
200 {
201 $col = new ColumnDefinition($name);
202 $col->enum($values);
203 $this->columns[] = $col;
204 return $col;
205 }
206
207 public function set($name, array $values)
208 {
209 $col = new ColumnDefinition($name);
210 $col->set($values);
211 $this->columns[] = $col;
212 return $col;
213 }
214
215 public function timestamp($name)
216 {
217 $col = new ColumnDefinition($name);
218 $col->timestamp();
219 $this->columns[] = $col;
220 return $col;
221 }
222
223 public function date($name)
224 {
225 $col = new ColumnDefinition($name);
226 $col->date();
227 $this->columns[] = $col;
228 return $col;
229 }
230
231 public function datetime($name)
232 {
233 $col = new ColumnDefinition($name);
234 $col->datetime();
235 $this->columns[] = $col;
236 return $col;
237 }
238
239 public function time($name)
240 {
241 $col = new ColumnDefinition($name);
242 $col->time();
243 $this->columns[] = $col;
244 return $col;
245 }
246
247 public function year($name)
248 {
249 $col = new ColumnDefinition($name);
250 $col->year();
251 $this->columns[] = $col;
252 return $col;
253 }
254
255 // ── Compound Shortcuts ───────────────────────────────────
256
257 public function timestamps()
258 {
259 $this->timestamp('created_at')->default('CURRENT_TIMESTAMP');
260 $this->timestamp('updated_at')->nullable();
261 }
262
263 public function softDeletes()
264 {
265 $this->timestamp('deleted_at')->nullable();
266 }
267
268 public function morphs($name)
269 {
270 $this->integer($name . '_id');
271 $this->string($name . '_type');
272 }
273
274 public function nullableMorphs($name)
275 {
276 $this->integer($name . '_id')->nullable();
277 $this->string($name . '_type')->nullable();
278 }
279
280 // ── Index Definitions ────────────────────────────────────
281
282 /**
283 * Add a regular index.
284 */
285 public function index($columns, $name = null)
286 {
287 $columns = (array) $columns;
288 $name = $name ?: implode('_', $columns);
289
290 if ($this->mode === 'create') {
291 $this->indexes[] = [
292 'type' => 'KEY',
293 'columns' => $columns,
294 'name' => $name,
295 ];
296 } else {
297 $this->operations[] = [
298 'type' => 'addIndex',
299 'data' => ['columns' => $columns, 'name' => $name],
300 ];
301 }
302
303 return $this;
304 }
305
306 /**
307 * Add a unique index.
308 */
309 public function unique($columns, $name = null)
310 {
311 $columns = (array) $columns;
312 $name = $name ?: 'unique_' . implode('_', $columns);
313
314 if ($this->mode === 'create') {
315 $this->indexes[] = [
316 'type' => 'UNIQUE KEY',
317 'columns' => $columns,
318 'name' => $name,
319 ];
320 } else {
321 $this->operations[] = [
322 'type' => 'addUniqueIndex',
323 'data' => ['columns' => $columns, 'name' => $name],
324 ];
325 }
326
327 return $this;
328 }
329
330 // ══════════════════════════════════════════════════════════
331 // ALTER MODE — Guarded Operations
332 // ══════════════════════════════════════════════════════════
333
334 /**
335 * Add a column (only if it doesn't exist).
336 */
337 public function addColumn($name)
338 {
339 $col = new ColumnDefinition($name);
340 $this->operations[] = [
341 'type' => 'addColumn',
342 'data' => $col,
343 ];
344 return $col;
345 }
346
347 /**
348 * Drop a column (only if it exists).
349 */
350 public function dropColumn($name)
351 {
352 $this->operations[] = [
353 'type' => 'dropColumn',
354 'data' => $name,
355 ];
356 return $this;
357 }
358
359 /**
360 * Modify a column type/size (only if it exists).
361 */
362 public function modifyColumn($name)
363 {
364 $col = new ColumnDefinition($name);
365 $this->operations[] = [
366 'type' => 'modifyColumn',
367 'data' => $col,
368 ];
369 return $col;
370 }
371
372 /**
373 * Rename a column (only if old exists and new doesn't).
374 */
375 public function renameColumn($from, $to)
376 {
377 $this->operations[] = [
378 'type' => 'renameColumn',
379 'data' => ['from' => $from, 'to' => $to],
380 ];
381 return $this;
382 }
383
384 /**
385 * Drop an index (only if it exists).
386 */
387 public function dropIndex($name)
388 {
389 $this->operations[] = [
390 'type' => 'dropIndex',
391 'data' => $name,
392 ];
393 return $this;
394 }
395
396 // ══════════════════════════════════════════════════════════
397 // EXECUTION
398 // ══════════════════════════════════════════════════════════
399
400 /**
401 * Execute CREATE TABLE via dbDelta (idempotent).
402 */
403 public function executeCreate()
404 {
405 $sql = $this->buildCreateSql();
406 return Schema::createTable($this->table, $sql);
407 }
408
409 /**
410 * Execute ALTER operations with guards.
411 */
412 public function executeAlter()
413 {
414 $results = [];
415 $tbl = Schema::table($this->table);
416
417 if (!Schema::hasTable($this->table)) {
418 return $results;
419 }
420
421 foreach ($this->operations as $op) {
422 switch ($op['type']) {
423 case 'addColumn':
424 /** @var ColumnDefinition $col */
425 $col = $op['data'];
426 if (!Schema::hasColumn($col->getName(), $this->table)) {
427 Schema::alterTable($this->table, $col->toAlterAddSql());
428 $results[] = "Added column {$col->getName()} to {$tbl}";
429 }
430 break;
431
432 case 'dropColumn':
433 $name = $op['data'];
434 if (Schema::hasColumn($name, $this->table)) {
435 Schema::query("ALTER TABLE {$tbl} DROP COLUMN {$name}");
436 $results[] = "Dropped column {$name} from {$tbl}";
437 }
438 break;
439
440 case 'modifyColumn':
441 /** @var ColumnDefinition $col */
442 $col = $op['data'];
443 if (Schema::hasColumn($col->getName(), $this->table)) {
444 Schema::alterTable($this->table, $col->toAlterModifySql());
445 $results[] = "Modified column {$col->getName()} in {$tbl}";
446 }
447 break;
448
449 case 'renameColumn':
450 $from = $op['data']['from'];
451 $to = $op['data']['to'];
452 if (Schema::hasColumn($from, $this->table) && !Schema::hasColumn($to, $this->table)) {
453 $currentDef = $this->getCurrentColumnDef($from);
454 if ($currentDef) {
455 Schema::query("ALTER TABLE {$tbl} CHANGE COLUMN {$from} {$to} {$currentDef}");
456 $results[] = "Renamed column {$from} to {$to} in {$tbl}";
457 }
458 }
459 break;
460
461 case 'addIndex':
462 $name = $op['data']['name'];
463 $columns = $op['data']['columns'];
464 if (!$this->hasIndex($name)) {
465 $colList = implode(', ', $columns);
466 Schema::query("ALTER TABLE {$tbl} ADD INDEX {$name} ({$colList})");
467 $results[] = "Added index {$name} on {$tbl}";
468 }
469 break;
470
471 case 'addUniqueIndex':
472 $name = $op['data']['name'];
473 $columns = $op['data']['columns'];
474 if (!$this->hasIndex($name)) {
475 $colList = implode(', ', $columns);
476 Schema::query("ALTER TABLE {$tbl} ADD UNIQUE INDEX {$name} ({$colList})");
477 $results[] = "Added unique index {$name} on {$tbl}";
478 }
479 break;
480
481 case 'dropIndex':
482 $name = $op['data'];
483 if ($this->hasIndex($name)) {
484 Schema::dropIndex($this->table, $name);
485 $results[] = "Dropped index {$name} from {$tbl}";
486 }
487 break;
488 }
489 }
490
491 return $results;
492 }
493
494 // ══════════════════════════════════════════════════════════
495 // INTERNAL HELPERS
496 // ══════════════════════════════════════════════════════════
497
498 /**
499 * Build the column+index SQL block for CREATE TABLE.
500 * Uses KEY (not INDEX) for dbDelta compatibility.
501 */
502 protected function buildCreateSql()
503 {
504 $lines = [];
505
506 $hasPrimaryKey = false;
507
508 foreach ($this->columns as $col) {
509 $sql = $col->toSql();
510
511 // dbDelta needs PRIMARY KEY on its own line
512 if (str_contains($sql, 'AUTO_INCREMENT')) {
513 $hasPrimaryKey = true;
514 }
515
516 $lines[] = $col->toSql();
517 }
518
519 // Add PRIMARY KEY line for auto-increment columns (dbDelta requirement)
520 if ($hasPrimaryKey) {
521 foreach ($this->columns as $col) {
522 if (str_contains($col->toSql(), 'AUTO_INCREMENT')) {
523 $lines[] = 'PRIMARY KEY (' . $col->getName() . ')';
524 break;
525 }
526 }
527 }
528
529 // Add index lines from column-level modifiers
530 foreach ($this->columns as $col) {
531 if ($col->isIndex()) {
532 $lines[] = 'KEY ' . $col->getName() . ' (' . $col->getName() . ')';
533 }
534 if ($col->isUnique() && !$col->isIndex()) {
535 $lines[] = 'UNIQUE KEY ' . $col->getName() . ' (' . $col->getName() . ')';
536 }
537 }
538
539 // Add index lines from explicit index() / unique() calls
540 foreach ($this->indexes as $idx) {
541 $colList = implode(', ', $idx['columns']);
542 $lines[] = $idx['type'] . ' ' . $idx['name'] . ' (' . $colList . ')';
543 }
544
545 return implode(",\n", $lines);
546 }
547
548 /**
549 * Check if an index exists on this table.
550 */
551 protected function hasIndex($indexName)
552 {
553 $tbl = Schema::table($this->table);
554
555 $rows = (array) Schema::db()->get_results("SHOW INDEX FROM {$tbl}");
556
557 foreach ($rows as $row) {
558 if ($row->Key_name === $indexName) {
559 return true;
560 }
561 }
562
563 return false;
564 }
565
566 /**
567 * Get the current column definition for CHANGE COLUMN.
568 */
569 protected function getCurrentColumnDef($column)
570 {
571 $tbl = Schema::table($this->table);
572
573 $rows = (array) Schema::db()->get_results("SHOW COLUMNS FROM {$tbl}");
574
575 $row = null;
576 foreach ($rows as $r) {
577 if ($r->Field === $column) {
578 $row = $r;
579 break;
580 }
581 }
582
583 if (!$row) {
584 return null;
585 }
586
587 $def = $row->Type;
588
589 if ($row->Null === 'NO') {
590 $def .= ' NOT NULL';
591 } else {
592 $def .= ' NULL';
593 }
594
595 if ($row->Default !== null) {
596 $default = $row->Default;
597 // SQLite's SHOW COLUMNS returns defaults with surrounding quotes
598 if (Schema::isSqlite() && preg_match("/^'(.*)'$/s", $default, $m)) {
599 $default = $m[1];
600 }
601 $def .= " DEFAULT '" . addslashes($default) . "'";
602 }
603
604 if (!empty($row->Extra) && str_contains($row->Extra, 'auto_increment')) {
605 $def .= ' AUTO_INCREMENT';
606 }
607
608 return $def;
609 }
610
611 public function getTable()
612 {
613 return $this->table;
614 }
615 }
616