Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F6020905
SchemaGenerator.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
5 KB
Referenced Files
None
Subscribers
None
SchemaGenerator.php
View Options
<?php
namespace
MediaWiki\Maintenance
;
use
Doctrine\SqlFormatter\NullHighlighter
;
use
Doctrine\SqlFormatter\SqlFormatter
;
use
JsonException
;
use
MediaWiki\DB\AbstractSchemaValidationError
;
use
MediaWiki\DB\AbstractSchemaValidator
;
use
Wikimedia\Rdbms\DoctrineSchemaBuilderFactory
;
/**
* Helper to generate abstract schema and schema changes in maintenance scripts.
*/
class
SchemaGenerator
{
/**
* Fetches the abstract schema.
*
* @param string $jsonPath
* @return array
* @throws AbstractSchemaValidationError
*/
public
function
validateAndGetSchema
(
string
$jsonPath
):
array
{
$json
=
file_get_contents
(
$jsonPath
);
if
(
!
$json
)
{
throw
new
AbstractSchemaValidationError
(
"'$jsonPath' does not exist!"
);
}
try
{
$abstractSchema
=
json_decode
(
$json
,
true
,
512
,
JSON_THROW_ON_ERROR
);
}
catch
(
JsonException
$e
)
{
throw
new
AbstractSchemaValidationError
(
"Invalid JSON schema: "
.
$e
->
getMessage
(),
0
,
$e
);
}
$validator
=
new
AbstractSchemaValidator
();
$validator
->
validate
(
$jsonPath
);
return
$abstractSchema
;
}
/**
* @throws AbstractSchemaValidationError
*/
public
function
generateSchema
(
string
$platform
,
string
$jsonPath
):
string
{
$abstractSchemaChange
=
$this
->
validateAndGetSchema
(
$jsonPath
);
$sql
=
$this
->
makeSQLComment
(
'generateSchemaSql.php'
,
$jsonPath
);
$schemaBuilder
=
(
new
DoctrineSchemaBuilderFactory
()
)->
getSchemaBuilder
(
$platform
);
foreach
(
$abstractSchemaChange
as
$table
)
{
$schemaBuilder
->
addTable
(
$table
);
}
$tableSqls
=
$schemaBuilder
->
getSql
();
$sql
.=
$this
->
cleanupSqlArray
(
$platform
,
$tableSqls
);
return
$sql
;
}
/**
* @throws AbstractSchemaValidationError
*/
public
function
generateSchemaChange
(
string
$platform
,
string
$jsonPath
):
string
{
$abstractSchemaChange
=
$this
->
validateAndGetSchema
(
$jsonPath
);
$sql
=
$this
->
makeSQLComment
(
'generateSchemaChangeSql.php'
,
$jsonPath
);
$schemaChangeBuilder
=
(
new
DoctrineSchemaBuilderFactory
()
)->
getSchemaChangeBuilder
(
$platform
);
$schemaChangeSqls
=
$schemaChangeBuilder
->
getSchemaChangeSql
(
$abstractSchemaChange
);
if
(
!
$schemaChangeSqls
)
{
throw
new
AbstractSchemaValidationError
(
"No schema changes detected!"
);
}
$sql
.=
$this
->
cleanupSqlArray
(
$platform
,
$schemaChangeSqls
);
return
$sql
;
}
private
function
makeSQLComment
(
string
$scriptName
,
string
$jsonPath
):
string
{
global
$IP
,
$wgExtensionDirectory
;
$canonicalJsonPath
=
$this
->
normalizePath
(
$jsonPath
,
$IP
,
$wgExtensionDirectory
);
return
"-- This file is automatically generated using maintenance/$scriptName.
\n
"
.
"-- Source: $canonicalJsonPath
\n
"
.
"-- Do not modify this file directly.
\n
"
.
"-- See https://www.mediawiki.org/wiki/Manual:Schema_changes
\n
"
;
}
/**
* Make absolute path relative to the root of $IP or extension
*
* @param string $jsonPath Path to an existing schema file
* @param string $IP Root of MediaWiki core installation
* @param string $extensionDirectory Root of dir holding MediaWiki extensions
* @return string the relative path
*/
public
static
function
normalizePath
(
string
$jsonPath
,
string
$IP
,
string
$extensionDirectory
):
string
{
$installPath
=
realpath
(
$IP
);
$jsonPath
=
realpath
(
$jsonPath
);
$extensionDirectory
=
realpath
(
$extensionDirectory
);
// For windows
if
(
DIRECTORY_SEPARATOR
===
'
\\
'
)
{
$installPath
=
strtr
(
$installPath
,
'
\\
'
,
'/'
);
$jsonPath
=
strtr
(
$jsonPath
,
'
\\
'
,
'/'
);
$extensionDirectory
=
strtr
(
$extensionDirectory
,
'
\\
'
,
'/'
);
}
if
(
str_starts_with
(
$jsonPath
,
$extensionDirectory
)
)
{
// Strip $wgExtensionDirectory which might be configured outside of $IP
// /path/to/mw-ext-all/Example/bar -> Example/bar
$canonicalJsonPath
=
str_replace
(
"$extensionDirectory/"
,
''
,
$jsonPath
);
// Strip the extension name
// Example/bar -> bar
$canonicalJsonPath
=
explode
(
'/'
,
$canonicalJsonPath
,
2
)[
1
];
}
else
{
// For mediawiki/core we strip $IP
// /path/to/mediawiki/foo/bar -> foo/bar
$canonicalJsonPath
=
str_replace
(
"$installPath/"
,
''
,
$jsonPath
);
}
return
$canonicalJsonPath
;
}
/**
* Takes the output of DoctrineSchemaBuilder::getSql() or
* DoctrineSchemaChangeBuilder::getSchemaChangeSql() and applies presentational changes.
*
* @param string $platform DB engine identifier
* @param array $sqlArray Array of SQL statements
* @return string
*/
private
function
cleanupSqlArray
(
string
$platform
,
array
$sqlArray
):
string
{
if
(
!
$sqlArray
)
{
return
''
;
}
// Temporary
$sql
=
implode
(
";
\n\n
"
,
$sqlArray
)
.
';'
;
$sql
=
(
new
SqlFormatter
(
new
NullHighlighter
()
)
)->
format
(
$sql
);
// Postgres hacks
if
(
$platform
===
'postgres'
)
{
// FIXME: Fix a lot of weird formatting issues caused by
// presence of partial index's WHERE clause, this should probably
// be done in some better way, but for now this can work temporarily
$sql
=
str_replace
(
[
"WHERE
\n
"
,
"
\n
/*_*/
\n
"
,
" "
,
" );"
,
"KEY(
\n
"
],
[
"WHERE"
,
' '
,
" "
,
');'
,
"KEY(
\n
"
],
$sql
);
}
// Temporary fixes until the linting issues are resolved upstream.
// https://github.com/doctrine/sql-formatter/issues/53
$sql
=
preg_replace
(
"!
\s
+/
\*
_
\*
/
\s
+!"
,
" /*_*/"
,
$sql
);
$sql
=
preg_replace
(
'!
\s
+/
\*\$
wgDBTableOptions
\*
/
\s
+;!'
,
' /*$wgDBTableOptions*/;'
,
$sql
);
$sql
=
str_replace
(
"; CREATE "
,
";
\n\n
CREATE "
,
$sql
);
$sql
=
str_replace
(
";
\n\n
CREATE TABLE "
,
";
\n\n\n
CREATE TABLE "
,
$sql
);
$sql
=
preg_replace
(
'/^(CREATE|DROP|ALTER)
\s
+(TABLE|VIEW|INDEX)
\s
+/m'
,
'$1 $2 '
,
$sql
);
$sql
=
preg_replace
(
'/(?<!
\s
|;)
\s
+(ADD|DROP|ALTER|MODIFY|CHANGE|RENAME)
\s
+/'
,
"
\n
\$
1 "
,
$sql
);
$sql
=
str_replace
(
"; "
,
";
\n
"
,
$sql
);
if
(
!
str_ends_with
(
$sql
,
"
\n
"
)
)
{
$sql
.=
"
\n
"
;
}
// Sqlite hacks
if
(
$platform
===
'sqlite'
)
{
// Doctrine prepends __temp__ to the table name and we set the table with the schema prefix causing invalid
// sqlite.
$sql
=
preg_replace
(
'/__temp__
\s
*
\/\*
_
\*\/
/'
,
'/*_*/__temp__'
,
$sql
);
}
return
$sql
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Oct 4 2026, 15:02 (5 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1f/6a/93b4c66e500a23321afc2540c4c6
Default Alt Text
SchemaGenerator.php (5 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment