Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
2af14ad9
Commit
2af14ad9
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 2 files via Son of Anton
parent
a45e66f7
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
152 additions
and
64 deletions
+152
-64
MigrationRunner.php
app/Core/Migration/MigrationRunner.php
+104
-33
SeederRunner.php
app/Core/Seeder/SeederRunner.php
+48
-31
No files found.
app/Core/Migration/MigrationRunner.php
View file @
2af14ad9
...
@@ -4,37 +4,52 @@ declare(strict_types=1);
...
@@ -4,37 +4,52 @@ declare(strict_types=1);
namespace
App\Core\Migration
;
namespace
App\Core\Migration
;
use
App\Core\Database
;
use
App\Core\Database
;
use
App\Core\Autoloader
;
final
class
MigrationRunner
final
class
MigrationRunner
{
{
private
Database
$db
;
private
Database
$db
;
private
string
$migrationsDir
;
public
function
__construct
(
Database
$db
)
public
function
__construct
(
Database
$db
)
{
{
$this
->
db
=
$db
;
$this
->
db
=
$db
;
$this
->
migrationsDir
=
dirname
(
__DIR__
,
2
)
.
'/../database/migrations'
;
$this
->
migrationsDir
=
realpath
(
$this
->
migrationsDir
)
?:
(
dirname
(
__DIR__
,
3
)
.
'/database/migrations'
);
}
}
public
function
migrate
()
:
array
public
function
migrate
()
:
array
{
{
$this
->
ensureMigrationsTable
();
$this
->
ensureMigrationsTable
();
$ran
=
$this
->
getRanMigrations
();
$executed
=
$this
->
getExecutedMigrations
();
$batch
=
$this
->
getNextBatch
();
$files
=
$this
->
getMigrationFiles
();
$files
=
$this
->
getMigrationFiles
();
$batch
=
$this
->
getNextBatch
();
$results
=
[];
$results
=
[];
foreach
(
$files
as
$file
)
{
foreach
(
$files
as
$file
)
{
$name
=
basename
(
$file
,
'.php'
);
$name
=
basename
(
$file
,
'.php'
);
if
(
in_array
(
$name
,
$
ran
))
{
if
(
in_array
(
$name
,
$
executed
))
{
continue
;
continue
;
}
}
echo
" Migrating:
{
$name
}
...
\n
"
;
$migration
=
require
$file
;
$migration
=
require
$file
;
if
(
is_array
(
$migration
)
&&
isset
(
$migration
[
'up'
]))
{
if
(
is_array
(
$migration
)
&&
isset
(
$migration
[
'up'
]))
{
$this
->
db
->
raw
(
$migration
[
'up'
]);
// Array format: ['up' => 'SQL...', 'down' => 'SQL...']
$upSql
=
$migration
[
'up'
];
// Handle multiple statements separated by semicolons
$statements
=
$this
->
splitStatements
(
$upSql
);
foreach
(
$statements
as
$stmt
)
{
$stmt
=
trim
(
$stmt
);
if
(
$stmt
!==
''
)
{
$this
->
db
->
raw
(
$stmt
);
}
}
}
elseif
(
is_object
(
$migration
)
&&
method_exists
(
$migration
,
'up'
))
{
}
elseif
(
is_object
(
$migration
)
&&
method_exists
(
$migration
,
'up'
))
{
$migration
->
up
(
$this
->
db
);
$migration
->
up
(
$this
->
db
);
}
elseif
(
is_callable
(
$migration
))
{
$migration
(
$this
->
db
);
}
}
$this
->
db
->
insert
(
'migrations'
,
[
$this
->
db
->
insert
(
'migrations'
,
[
...
@@ -52,30 +67,44 @@ final class MigrationRunner
...
@@ -52,30 +67,44 @@ final class MigrationRunner
public
function
rollback
()
:
array
public
function
rollback
()
:
array
{
{
$this
->
ensureMigrationsTable
();
$this
->
ensureMigrationsTable
();
$
b
atch
=
$this
->
getLastBatch
();
$
lastB
atch
=
$this
->
getLastBatch
();
if
(
$
b
atch
===
0
)
{
if
(
$
lastB
atch
===
0
)
{
return
[];
return
[];
}
}
$
migration
s
=
$this
->
db
->
select
(
$
row
s
=
$this
->
db
->
select
(
"SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC"
,
"SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC"
,
[
$
b
atch
]
[
$
lastB
atch
]
);
);
$results
=
[];
$results
=
[];
foreach
(
$migrations
as
$row
)
{
foreach
(
$rows
as
$row
)
{
$file
=
$this
->
getMigrationDir
()
.
'/'
.
$row
[
'migration'
]
.
'.php'
;
$name
=
$row
[
'migration'
];
$file
=
$this
->
migrationsDir
.
'/'
.
$name
.
'.php'
;
if
(
file_exists
(
$file
))
{
if
(
file_exists
(
$file
))
{
echo
" Rolling back:
{
$name
}
...
\n
"
;
$migration
=
require
$file
;
$migration
=
require
$file
;
if
(
is_array
(
$migration
)
&&
isset
(
$migration
[
'down'
]))
{
if
(
is_array
(
$migration
)
&&
isset
(
$migration
[
'down'
]))
{
$this
->
db
->
raw
(
$migration
[
'down'
]);
$statements
=
$this
->
splitStatements
(
$migration
[
'down'
]);
foreach
(
$statements
as
$stmt
)
{
$stmt
=
trim
(
$stmt
);
if
(
$stmt
!==
''
)
{
try
{
$this
->
db
->
raw
(
$stmt
);
}
catch
(
\Throwable
$e
)
{
echo
" ⚠️ Rollback warning for
{
$name
}
: "
.
$e
->
getMessage
()
.
"
\n
"
;
}
}
}
}
elseif
(
is_object
(
$migration
)
&&
method_exists
(
$migration
,
'down'
))
{
}
elseif
(
is_object
(
$migration
)
&&
method_exists
(
$migration
,
'down'
))
{
$migration
->
down
(
$this
->
db
);
$migration
->
down
(
$this
->
db
);
}
}
}
}
$this
->
db
->
delete
(
'migrations'
,
'`migration` = ?'
,
[
$row
[
'migration'
]
]);
$this
->
db
->
query
(
"DELETE FROM migrations WHERE migration = ?"
,
[
$name
]);
$results
[]
=
$
row
[
'migration'
]
;
$results
[]
=
$
name
;
}
}
return
$results
;
return
$results
;
...
@@ -84,7 +113,7 @@ final class MigrationRunner
...
@@ -84,7 +113,7 @@ final class MigrationRunner
public
function
status
()
:
array
public
function
status
()
:
array
{
{
$this
->
ensureMigrationsTable
();
$this
->
ensureMigrationsTable
();
$
ran
=
$this
->
getRan
Migrations
();
$
executed
=
$this
->
getExecuted
Migrations
();
$files
=
$this
->
getMigrationFiles
();
$files
=
$this
->
getMigrationFiles
();
$status
=
[];
$status
=
[];
...
@@ -92,7 +121,7 @@ final class MigrationRunner
...
@@ -92,7 +121,7 @@ final class MigrationRunner
$name
=
basename
(
$file
,
'.php'
);
$name
=
basename
(
$file
,
'.php'
);
$status
[]
=
[
$status
[]
=
[
'migration'
=>
$name
,
'migration'
=>
$name
,
'status'
=>
in_array
(
$name
,
$
ran
)
?
'Ran'
:
'Pending'
,
'status'
=>
in_array
(
$name
,
$
executed
)
?
'Ran'
:
'Pending'
,
];
];
}
}
...
@@ -101,9 +130,12 @@ final class MigrationRunner
...
@@ -101,9 +130,12 @@ final class MigrationRunner
private
function
ensureMigrationsTable
()
:
void
private
function
ensureMigrationsTable
()
:
void
{
{
if
(
!
$this
->
db
->
tableExists
(
'migrations'
))
{
try
{
$this
->
db
->
selectOne
(
"SELECT 1 FROM migrations LIMIT 1"
);
}
catch
(
\Throwable
$e
)
{
// Table doesn't exist — create it
$this
->
db
->
raw
(
"
$this
->
db
->
raw
(
"
CREATE TABLE `migrations` (
CREATE TABLE
IF NOT EXISTS
`migrations` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`migration` VARCHAR(255) NOT NULL,
`migration` VARCHAR(255) NOT NULL,
`batch` INT UNSIGNED NOT NULL,
`batch` INT UNSIGNED NOT NULL,
...
@@ -114,10 +146,29 @@ final class MigrationRunner
...
@@ -114,10 +146,29 @@ final class MigrationRunner
}
}
}
}
private
function
get
Ran
Migrations
()
:
array
private
function
get
Executed
Migrations
()
:
array
{
{
$rows
=
$this
->
db
->
select
(
"SELECT migration FROM migrations ORDER BY id"
);
try
{
return
array_column
(
$rows
,
'migration'
);
$rows
=
$this
->
db
->
select
(
"SELECT migration FROM migrations ORDER BY id"
);
return
array_column
(
$rows
,
'migration'
);
}
catch
(
\Throwable
$e
)
{
return
[];
}
}
private
function
getMigrationFiles
()
:
array
{
if
(
!
is_dir
(
$this
->
migrationsDir
))
{
echo
" ⚠️ Migrations directory not found:
{
$this
->
migrationsDir
}
\n
"
;
return
[];
}
$files
=
glob
(
$this
->
migrationsDir
.
'/Phase_*.php'
);
if
(
$files
===
false
||
empty
(
$files
))
{
return
[];
}
sort
(
$files
);
// Lexicographic sort: Phase_01_001 < Phase_02_001 < Phase_03_001
return
$files
;
}
}
private
function
getNextBatch
()
:
int
private
function
getNextBatch
()
:
int
...
@@ -127,23 +178,43 @@ final class MigrationRunner
...
@@ -127,23 +178,43 @@ final class MigrationRunner
private
function
getLastBatch
()
:
int
private
function
getLastBatch
()
:
int
{
{
$row
=
$this
->
db
->
selectOne
(
"SELECT MAX(batch) as max_batch FROM migrations"
);
try
{
return
(
int
)
(
$row
[
'max_batch'
]
??
0
);
$row
=
$this
->
db
->
selectOne
(
"SELECT MAX(batch) as max_batch FROM migrations"
);
return
(
int
)
(
$row
[
'max_batch'
]
??
0
);
}
catch
(
\Throwable
$e
)
{
return
0
;
}
}
}
private
function
getMigrationFiles
()
:
array
/**
* Split SQL string into individual statements.
* Handles semicolons inside the SQL properly.
*/
private
function
splitStatements
(
string
$sql
)
:
array
{
{
$dir
=
$this
->
getMigrationDir
();
$sql
=
trim
(
$sql
);
$files
=
glob
(
$dir
.
'/Phase_*.php'
);
if
(
$sql
===
''
)
{
if
(
$files
===
false
)
{
return
[];
return
[];
}
}
sort
(
$files
);
return
$files
;
}
private
function
getMigrationDir
()
:
string
// Simple split on semicolons followed by whitespace/newline
{
// This handles 99% of cases for DDL statements
return
Autoloader
::
basePath
()
.
'/database/migrations'
;
$statements
=
preg_split
(
'/;\s*\n/'
,
$sql
);
if
(
$statements
===
false
)
{
return
[
$sql
];
}
// Clean up: remove trailing semicolons from last statement
$result
=
[];
foreach
(
$statements
as
$stmt
)
{
$stmt
=
trim
(
$stmt
);
$stmt
=
rtrim
(
$stmt
,
';'
);
$stmt
=
trim
(
$stmt
);
if
(
$stmt
!==
''
)
{
$result
[]
=
$stmt
;
}
}
return
$result
;
}
}
}
}
\ No newline at end of file
app/Core/Seeder/SeederRunner.php
View file @
2af14ad9
...
@@ -4,15 +4,17 @@ declare(strict_types=1);
...
@@ -4,15 +4,17 @@ declare(strict_types=1);
namespace
App\Core\Seeder
;
namespace
App\Core\Seeder
;
use
App\Core\Database
;
use
App\Core\Database
;
use
App\Core\Autoloader
;
final
class
SeederRunner
final
class
SeederRunner
{
{
private
Database
$db
;
private
Database
$db
;
private
string
$seedsDir
;
public
function
__construct
(
Database
$db
)
public
function
__construct
(
Database
$db
)
{
{
$this
->
db
=
$db
;
$this
->
db
=
$db
;
$this
->
seedsDir
=
dirname
(
__DIR__
,
2
)
.
'/../database/seeds'
;
$this
->
seedsDir
=
realpath
(
$this
->
seedsDir
)
?:
(
dirname
(
__DIR__
,
3
)
.
'/database/seeds'
);
}
}
public
function
run
()
:
array
public
function
run
()
:
array
...
@@ -22,28 +24,42 @@ final class SeederRunner
...
@@ -22,28 +24,42 @@ final class SeederRunner
$files
=
$this
->
getSeedFiles
();
$files
=
$this
->
getSeedFiles
();
$results
=
[];
$results
=
[];
echo
" Seeds directory:
{
$this
->
seedsDir
}
\n
"
;
echo
" Found "
.
count
(
$files
)
.
" seed file(s)
\n
"
;
echo
" Already executed: "
.
count
(
$executed
)
.
"
\n
"
;
foreach
(
$files
as
$file
)
{
foreach
(
$files
as
$file
)
{
$name
=
basename
(
$file
,
'.php'
);
$name
=
basename
(
$file
,
'.php'
);
if
(
in_array
(
$name
,
$executed
))
{
if
(
in_array
(
$name
,
$executed
))
{
continue
;
continue
;
}
}
echo
" Running:
{
$name
}
...
\n
"
;
echo
" Seeding:
{
$name
}
...
\n
"
;
$seed
=
require
$file
;
try
{
$seed
=
require
$file
;
if
(
is_callable
(
$seed
))
{
$seed
(
$this
->
db
);
if
(
is_callable
(
$seed
))
{
}
elseif
(
is_object
(
$seed
)
&&
method_exists
(
$seed
,
'run'
))
{
$seed
(
$this
->
db
);
$seed
->
run
(
$this
->
db
);
}
elseif
(
is_object
(
$seed
)
&&
method_exists
(
$seed
,
'run'
))
{
$seed
->
run
(
$this
->
db
);
}
else
{
echo
" ⚠️ Seed
{
$name
}
returned non-callable, non-object. Skipping.
\n
"
;
continue
;
}
$this
->
db
->
insert
(
'seeds'
,
[
'seed'
=>
$name
,
'executed_at'
=>
date
(
'Y-m-d H:i:s'
),
]);
$results
[]
=
$name
;
echo
" ✔
{
$name
}
done
\n
"
;
}
catch
(
\Throwable
$e
)
{
echo
" ❌ SEED FAILED:
{
$name
}
— "
.
$e
->
getMessage
()
.
"
\n
"
;
echo
" File: "
.
$e
->
getFile
()
.
":"
.
$e
->
getLine
()
.
"
\n
"
;
// Continue to next seed — don't let one failure kill all seeds
}
}
$this
->
db
->
insert
(
'seeds'
,
[
'seed'
=>
$name
,
'executed_at'
=>
date
(
'Y-m-d H:i:s'
),
]);
$results
[]
=
$name
;
}
}
return
$results
;
return
$results
;
...
@@ -74,7 +90,6 @@ final class SeederRunner
...
@@ -74,7 +90,6 @@ final class SeederRunner
$seed
->
run
(
$this
->
db
);
$seed
->
run
(
$this
->
db
);
}
}
// Record it (ignore duplicate)
$name
=
basename
(
$targetFile
,
'.php'
);
$name
=
basename
(
$targetFile
,
'.php'
);
$existing
=
$this
->
db
->
selectOne
(
"SELECT id FROM seeds WHERE seed = ?"
,
[
$name
]);
$existing
=
$this
->
db
->
selectOne
(
"SELECT id FROM seeds WHERE seed = ?"
,
[
$name
]);
if
(
!
$existing
)
{
if
(
!
$existing
)
{
...
@@ -87,10 +102,9 @@ final class SeederRunner
...
@@ -87,10 +102,9 @@ final class SeederRunner
private
function
ensureSeedsTable
()
:
void
private
function
ensureSeedsTable
()
:
void
{
{
if
(
!
$this
->
db
->
tableExists
(
'seeds'
))
{
try
{
// Seeds table might not exist if migrations haven't created it
$this
->
db
->
selectOne
(
"SELECT 1 FROM seeds LIMIT 1"
);
// The schema.sql has it, but individual migrations might not
}
catch
(
\Throwable
$e
)
{
// Create it directly
$this
->
db
->
raw
(
"
$this
->
db
->
raw
(
"
CREATE TABLE IF NOT EXISTS `seeds` (
CREATE TABLE IF NOT EXISTS `seeds` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
...
@@ -104,23 +118,26 @@ final class SeederRunner
...
@@ -104,23 +118,26 @@ final class SeederRunner
private
function
getExecutedSeeds
()
:
array
private
function
getExecutedSeeds
()
:
array
{
{
$rows
=
$this
->
db
->
select
(
"SELECT seed FROM seeds ORDER BY id"
);
try
{
return
array_column
(
$rows
,
'seed'
);
$rows
=
$this
->
db
->
select
(
"SELECT seed FROM seeds ORDER BY id"
);
return
array_column
(
$rows
,
'seed'
);
}
catch
(
\Throwable
$e
)
{
return
[];
}
}
}
private
function
getSeedFiles
()
:
array
private
function
getSeedFiles
()
:
array
{
{
$dir
=
$this
->
getSeedDir
();
if
(
!
is_dir
(
$this
->
seedsDir
))
{
$files
=
glob
(
$dir
.
'/Phase_*.php'
);
echo
" ⚠️ Seeds directory not found:
{
$this
->
seedsDir
}
\n
"
;
if
(
$files
===
false
)
{
return
[];
}
$files
=
glob
(
$this
->
seedsDir
.
'/Phase_*.php'
);
if
(
$files
===
false
||
empty
(
$files
))
{
return
[];
return
[];
}
}
sort
(
$files
);
sort
(
$files
);
return
$files
;
return
$files
;
}
}
private
function
getSeedDir
()
:
string
{
return
Autoloader
::
basePath
()
.
'/database/seeds'
;
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment