* $schema = new ezcDbSchema();
* $schema->load( 'file.php', 'php-file' );
* $schema->save( 'file.xml', 'xml-file' );
*
*
* A more complex example:
*
* class MyAppSchema extends ezcDbSchema { ... }
*
* $schema1 = new MyAppSchema;
* $schema2 = new MyAppSchema;
*
* $schema1->load( 'file1.xml', 'xml-file' );
* $schema2->load( $db, 'oracle-db' );
*
* $diff = $schema1->compare( $schema2 );
* $schema1->saveDelta( $diff, 'delta.sql', 'mysql-file' );
*
* $schema2->save( 'schema2.sql', 'oracle-file' );
*
*
* @package DatabaseSchema
*/
class ezcDbSchema
{
/**
* Schema array.
*/
private $Schema;
/**
* Handles different formats for external schema storage.
*/
private $HandlerManager;
/**
* User-specified schema transformation function name.
*
* This function is called when the schema needs to be transformed
* (e.g. from mysql to pgsql).
* After the function finishes its execution, some standard transformations
* are performed (if needed).
*/
private $TransformationHook;
/**
* User specified schema scanning function name.
*/
private $FeaturesDetectionHook;
/**
* Constructs schema objects, initializing it with specified parameters.
*
* @param array $params Misc parameters
*
* There could be two types of information in the parameters:
* - user-specified hook for transforming schema before saving or comparing.
* - user-specified hook for detecting "schema features".
* - user-specified schema handlers.
*
* Exampple:
*
*
* class MyClass
* {
* public function detectFeatures( array &$schema ) { ... }
* public function transformSchema( array &$schema, array $targetSchema ) { ... }
* ...
* }
*
* $schema = new ezcDbSchema(
* array( 'transformation-hook' => 'MyClass::transformSchema',
* 'features-detection-hook' => 'MyClass::detectFeatures',
* 'user-handlers' => array( 'MyOracleSchemaHandler', 'MyDB2SchemaHandler' ) )
* );
*
*/
public function __construct( $params = array() )
{
$this->TransformationHook = '';
$this->FeaturesDetectionHook = '';
if ( isset( $params['transformation-hook'] ) )
{
$this->TransformationHook = $params['transformation-hook'];
unset( $params['transformation-hook'] );
}
if ( isset( $params['features-detection-hook'] ) )
{
$this->FeaturesDetectionHook = $params['features-detection-hook'];
unset( $params['features-detection-hook'] );
}
$this->HandlerManager = new ezcDbSchemaHandlerManager( $params );
$this->Schema = array();
}
/**
* Loads schema from the given source.
* @param mixed $src Source to load schema from.
* @param string $storageType Schema storage type.
* @param string $what What to load. Possible values:
* 'none', 'schema', 'data', 'both'.
* Default value is 'schema'.
* @returns bool true on success, false otherwise.
*/
public function load( $src, $storageType, $what = 'schema' )
{
$schema = $this->HandlerManager->loadSchema( $src, $storageType, $what );
if ( isset( $schema['schema'] ) )
{
$this->Schema['schema'] = $schema['schema'];
$this->detectFeatures();
}
if ( isset( $schema['data'] ) )
$this->Schema['data'] = $schema['data'];
}
/**
* Determine which features does the schema contain.
*
* The following features are recognized for MySQL:
* - auto_increment : Schema contains fields declared
* as AUTO_INCREMENT
* - field_precision : Precision is specified for some
* (usually numeric) fields
* - limited_index_field_length : There are indexes having constraint on
* maximum indexed string length.
*
* The following features are recognized for PostgreSQL:
* - sequences : Schema contains sequences(s).
* - triggers : Schema contains trigger(s).
* - field_precision : Precision is specified for some
* (usually numeric) fields
* - field_default_is_sequence_value : Some tables in the schema contain
* fields for which default value is
* determined by a sequence.
*/
protected function detectFeatures()
{
$schema =& $this->Schema['schema'];
$info =& $schema['_info'];
$features =& $info['features'];
if ( $info['dbms_type'] == 'mysql' )
{
foreach ( $schema['tables'] as $tableName => $tableSchema )
{
foreach ( $tableSchema['fields'] as $fieldName => $fieldSchema )
{
if ( isset( $fieldSchema['options']['auto_increment'] ) )
{
$features['auto_increment'] = true;
}
if ( isset( $fieldSchema['precision'] ) )
{
$features['field_precision'] = true;
}
}
foreach ( $tableSchema['indexes'] as $indexName => $indexSchema )
{
if ( isset( $indexSchema['options']['limitations'] ) )
{
$features['limited_index_field_length'] = true;
}
}
}
}
elseif ( $info['dbms_type'] == 'pgsql' )
{
// detect fields for which default value is determined by a sequence
$features =& $schema['_info']['features'];
foreach ( $schema['tables'] as $tableName => $tableSchema )
{
foreach ( $tableSchema['fields'] as $fieldName => $fieldSchema )
{
if ( isset( $fieldSchema['precision'] ) )
{
$features['field_precision'] = true;
}
if ( isset( $fieldSchema['options']['default_nextval'] ) )
{
$features['field_default_is_sequence_value'] = true;
}
}
}
if ( isset( $schema['sequences'] ) && is_array( $schema['sequences'] ) && count( $schema['sequences'] ) > 0 )
$features['sequences'] = true;
if ( isset( $schema['triggers'] ) && is_array( $schema['triggers'] ) && count( $schema['triggers'] ) > 0 )
$features['triggers'] = true;
}
// Run custom features detection hook (if specified by user).
if ( $this->FeaturesDetectionHook )
{
eval( "{$this->FeaturesDetectionHook}( \$this->Schema );" );
}
}
/**
* Saves schema to the given destination.
* @param mixed $dst Destination to save schema to.
* @param string $storageType Schema storage type.
* @param string $what What to save. Possible values:
* 'none', 'schema', 'data', 'both'.
* Default value is 'schema'.
* @returns bool true on success, false otherwise.
*/
public function save( $dst, $storageType, $what = 'schema' )
{
$targetHandler = $this->HandlerManager->getHandler( $storageType );
if ( !$targetHandler->needsSchemaTransformation() )
$schema = $this->Schema;
else
{
// run schema transformations
$targetSchemaInfo = array(
'schema' => array(
'_info' => array(
'dbms_type' => $targetHandler instanceof ezcDbSchemaHandlerSql ?
$targetHandler->getDbmsName() : false,
'dbms_ver' => false,
'app' => false,
'features' => $targetHandler->getSupportedFeatures()
),
)
);
$schema = $this->transform( $targetSchemaInfo );
}
// actually save it
$this->HandlerManager->saveSchema( $schema, $dst, $storageType, $what );
}
/**
* Return schema in one of internal formats without saving it to a file or database.
*
* For example, you might want to get schema as XML string, or DOM tree,
* or as a set of SQL queries, or as PHP array.
*
* @param string $internalFormat Format you want to get schema in.
* @param string $what What to get. Possible values:
* 'none', 'schema', 'data', 'both'.
* Default value is 'schema'.
* @returns mixed Schema in the specified format, false on error.
*
* @see getSupportedInternalFormats()
*/
public function get( $internalFormat = 'php-array', $what = 'schema' )
{
if ( $internalFormat != 'php-array' )
return $this->HandlerManager->getSchema( $this->Schema, $internalFormat, $what );
// php-array is requested
$schema = $this->Schema;
// remove unwanted info from the result.
if ( !in_array( $what, array( 'schema', 'both' ) ) )
unset( $schema['schema'] );
if ( !in_array( $what, array( 'data', 'both' ) ) )
unset( $schema['data'] );
return $schema;
}
/**
* Manually set schema.
*
* @params array $schema Schema to set.
*/
public function set( array $schema )
{
$this->Schema = $schema;
}
/**
* Saves difference between schemas in the specified format.
* @param array $delta Difference to save.
* @param mixed $dst Destination to save to.
* @param string $storageType Schema storage type.
*/
public function saveDelta( $delta, $dst, $storageType )
{
$this->HandlerManager->saveDelta( $delta, $dst, $storageType );
}
/**
* Compares the schema with another schema.
* @returns array Array containing delta (differencies).
*/
public function compare( ezcDbSchema $otherSchema )
{
$schema2 = $otherSchema->get( 'php-array', 'schema' );
if ( !isset( $this->Schema['schema'] ) || !isset( $schema2['schema'] ) )
{
throw new ezcDbSchemaException(
ezcDbSchemaException::GENERIC_ERROR,
'You should load schema before using it.' );
}
$transformedOtherSchema = $otherSchema->transform( $this->Schema );
return ezcDbSchemaComparator::compareSchemas(
$this->Schema['schema'],
$transformedOtherSchema['schema']
);
}
/**
* Transform schema.
*
* Transforms the schema accordingly to the given parameters.
* The original schema remains untouched.
* The resulting schema array is returned.
*
* @return array Resulting schema after transformation.
*/
protected function transform( array $targetSchema )
{
$schema = $this->Schema; // make a copy
// Run custom transformation hook.
if ( $this->TransformationHook )
{
eval( "{$this->TransformationHook}( \$schema, \$targetSchema );" );
}
// Run standard transformations.
ezcDbSchemaTransformations::run( $schema, $targetSchema );
$schema['schema']['_info']['dbms_type'] = $targetSchema['schema']['_info']['dbms_type'];
return $schema;
}
/**
* Returns list of storage types supported by all known handlers.
*/
public function getSupportedStorageTypes()
{
return $this->HandlerManager->getSupportedStorageTypes();
}
/**
* Return list of supported internal schema formats.
*
* @see get()
*/
public function getSupportedInternalFormats()
{
$formats = $this->HandlerManager->getSupportedInternalFormats();
array_unshift( $formats, 'php-array' );
return $formats;
}
/**
* Bulk data transfer.
*
* Transfers data from the given source to the given destination.
* The full data is never loaded to memory, it is transferred by small chunks.
*/
public function transferData( $src, $srcStorageType, $dst, $dstStorageType )
{
$srcHandler = $this->HandlerManager->getHandler( $srcStorageType );
$dstHandler = $this->HandlerManager->getHandler( $dstStorageType );
if ( ! $srcHandler instanceof ezcDbSchemaHandlerDataTransfer ||
! $dstHandler instanceof ezcDbSchemaHandlerDataTransfer )
{
throw new ezcDbSchemaException(
ezcDbSchemaException::GENERIC_ERROR,
"Bulk data transfers between storages of type ".
"'$srcStorageType and '$dstStorageType' " .
"are not supported." );
}
$dstHandler->openTransferDestination( $dst, $dstStorageType );
$srcHandler->transfer( $src, $srcStorageType, $dstHandler );
$dstHandler->closeTransferDestination();
}
/**
* Clear schema as if it had not been loaded before.
*/
public function reset()
{
$this->Schema = array();
}
}
?>