View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFPROP:Proposal Zone Template}

{zone-data:component-name}
Zend_Db_Schema_Manager
{zone-data}

{zone-data:proposer-list}
[Sean P. O. MacCath-Moran|mailto:zendDbSchemaManager@emanaton.com]
{zone-data}

{zone-data:liaison}
TBD
{zone-data}

{zone-data:revision}
1.0 - 1 January 2008: Initial Draft.
{zone-data}

{zone-data:overview}
Zend_Db_Schema_Manager provides the means for one to structure database upgrades / downgrades (i.e. "migrations") and copies (i.e. "replications") while being database agnostic and remaining flexible with regard to how db changes are defined, how the migration status is stored, and how db changes are executed.
{zone-data}

{zone-data:references}
* [Original proposal by Rob Allen and Thomas Weidner|http://framework.zend.com/wiki/display/ZFPROP/Zend_Db_Schema_Manager+-+Rob+Allen]
* [Ruby On Rails Class ActiveRecord::Migration|http://api.rubyonrails.org/classes/ActiveRecord/Migration.html]
* [Phing DBDeploy|http://phing.info/docs/guide/current/chapters/appendixes/AppendixC-OptionalTasks.html#DbDeployTask]
{zone-data}

{zone-data:requirements}
* This component *will* execute defined 'migration steps' (upgrade / downgrade database).
* This component *will* examine a database and create a schema map of it which may be used to create a copy of that database (i.e. "replicate") in a database agnostic fashion.
* This component *will* provide extended versions of every supported database adapter with various database manipulation functions available (e.g. addTable(), renameColumn(), deleteIndex(), etc.). Users of custom adapters can easily extend the given abstract class.
* This component *will* allow database changes to be created as PHP classes, or config files (ini, xml, and soon I hope, yaml), or through any means that implements the given interface/abstract.
* This component *will* allow migration status information to be stored in a DB, or config file, or through any means that implements the given interface/abstract.
* This component *will* pull in migrations to be executed from a given path, of from the include path (with some search criteria defined), or from a class prefix (using the autoloader), or through any means that implements the given interface/abstract.
* This component *will* allow database change steps to be ordered by
** numbering (either pulled from the class/file name or by a property set within the source file).
** datetime (either pulled from the class/file name or by a property set within the source file).
* This component *will* provide classes to generate database change step source files in all supported formats.
* This component *will* allow default columns to be automatically added to all tables, or to prevent the default columns from being added to a partcular table.
{zone-data}

{zone-data:dependencies}
* Zend_Exception
* Zend_Db
* Zend_Config
* Zend_Loader
* Zend_CodeGenerator
{zone-data}

{zone-data:operation}
The Zend Database Schema Manager provides the means to organize and execute database upgrades, downgrades, and migrations.

Each database 'migration step', if you will, is stored in a file that contains the 'upgrade' operation and its reversing 'downgrade' operation; e.g. if a step creates a table on an 'upgrade', it is deleted in the 'downgrade'. This provides the means to build up and tear down a database in a controlled fashion, allowing steps to be arbitrarily applied and un-applied. Each file may a PHP class (the extends the provided abstract class), a Zend_Config readable file (that follows a prescribed format), or a class which implements the provided interface/abstract.

Each step contains within it the means to sort it among its peers. The sorting mechanism may be based on a numeric sequence or on date-time stamps, but all the steps must use the same mechanism; the same set of steps cannot mix sorting schemes. The sort schema to be used may either be set explicitly in the Db_Schema_Manager or it may be detected by the system. The sort values may either be set via properties internal to the step definition or encoded into the name class and/or file name of the step source.

The Db_Schema_Manager must have means defined for storing status information. By default, this is done within the database being maintained. Unless configured to do otherwise, the manager will automatically create a table for this purpose. Other storage options include providing the manager with a Zend_Config object or defining a custom storage mechanism by implementing the provided interface/abstract.

Upon initializing the storage location, all of the currently defined steps are read, their ordering determined, and references to each are added to the storage. The Db Schema Manager can refresh the storage on command, loading all the available steps and adding those that are missing (or restarting from scratch if so directed).

The Database Schema Manager can be directed to up/down-grade to a specific step number (or datetime) or to upgrade to the latest available. Each step's up/down-grade code is called in sequence according to the sort order. The process can be configured to halt on errors, to ignore errors and return a list of issues when completed, and/or to back out of the upgrade when an error occurs by rolling back the current transaction (if rollback is available) executing all the reversing down/up-grade steps.

A database can be replicated, copying all the table structures with their columns and indexes from one database (e.g. MySQL) to another (e.g. Oracle). This provides the means to easily implement the Db_Schema_Manager on a project that is already in progress. The database scheme may be stored in any of the formats defined for storing migration steps; when the directive to copy the database is given, the destination may be another database, a PHP class file, or a Zend_Config compatible file (i.e. ini or xml for now).
{zone-data}

{zone-data:milestones}
* Milestone 1: Proposal finished
* Milestone 2: Proposal accepted
* Milestone 3: Working implementation
* Milestone 4: Unit tests
* Milestone 5: Documentation
* Milestone 6: Moved to core
{zone-data}

{zone-data:class-list}

Zend_Db_Schema_Exception
Zend_Db_Schema_Manager
Zend_Db_Schema_Replicate

Zend_Db_Schema_Adapter (factory class)
Zend_Db_Schema_Adapter_Abstract
Zend_Db_Schema_Adapter_Exception

Zend_Db_Schema_Adapter_Db2
Zend_Db_Schema_Adapter_Mysql
Zend_Db_Schema_Adapter_Oracle
Zend_Db_Schema_Adapter_Pdo_Ibm
Zend_Db_Schema_Adapter_Pdo_Mssql
Zend_Db_Schema_Adapter_Pdo_Mysql
Zend_Db_Schema_Adapter_Pdo_Oci
Zend_Db_Schema_Adapter_Pdo_Pgsql
Zend_Db_Schema_Adapter_Pdo_Sqlite

Zend_Db_Schema_Makestep (factory class)
Zend_Db_Schema_Makestep_Abstract
Zend_Db_Schema_Makestep_Exception

Zend_Db_Schema_Makestep_Config
Zend_Db_Schema_Makestep_Db
Zend_Db_Schema_Makestep_Php

Zend_Db_Schema_Migrate (factory class)
Zend_Db_Schema_Migrate_Exception
Zend_Db_Schema_Migrate_Abstract
Zend_Db_Schema_Migrate_Config
Zend_Db_Schema_Migrate_Step

Zend_Db_Schema_Source (factory class)
Zend_Db_Schema_Source_Abstract
Zend_Db_Schema_Source_Exception

Zend_Db_Schema_Source_Autoloader
Zend_Db_Schema_Source_IncludePath
Zend_Db_Schema_Source_Path

Zend_Db_Schema_Storage (factory class)
Zend_Db_Schema_Storage_Exception

Zend_Db_Schema_Storage_Abstract
Zend_Db_Schema_Storage_Cache
Zend_Db_Schema_Storage_Config
Zend_Db_Schema_Storage_Db
{zone-data}

{zone-data:use-cases}
||UC-01||
The simple use case could be starting project and building up the user table over several steps. In this case PHP classes are used so that data transformations can be easily accomplished, and the classes are named as though they were in the "Schema" directory of a module (i.e. much like the "form" or "table" directories). While it would be odd to mix sort designation conventions in a file, it is being done so here for the sake of demonstration (i.e. some sorts are designated in the class name while others are set via properties).


{code}
<?php

class Miscmod_Schema_01AddUserTable extends Zend_Db_Schema_Migrate_Step
{
/**
* Create the user table with user_uuid and name columns.
*
*/
public function up()
{
$this->getDb()->createTable('user', array(
'user_uuid'=>array(
'type'=>'CHAR',
'length'=>'36',
'primary'=>true,
),
'name'=>array(
'type'=>'VARCHAR',
'length'=>'64',
),
));
}

/**
* Rollback changes made in up().
*
* Drop the user table.
*
*/
public function down()
{
$this->getDb()->dropTable('user');
}
}
{code}

{code}
<?php

class Miscmod_Schema_AddUserTableColumnPassword extends Zend_Db_Schema_Migrate_Step
{
protected $_step = 2;
/**
* Add password column to the user table.
*
*/
public function up()
{
$this->getDb()->addColumns('user', array(
'password'=>array(
'type'=>'CHAR',
'length'=>'36',
),
));
}

/**
* Rollback changes made in up().
*
* Drop the password column from the user table.
*/
public function down()
{
$this->getDb()->dropColumns('user', array('password'));
}
}
{code}

{code}
<?php

class Miscmod_Schema_AddUserTableColumnPasswordSalt extends Zend_Db_Schema_Migrate_Step
{
protected $_step = 3;
/**
* Add password_salt column to the user table.
*
*/
public function up()
{
$this->getDb()->addColumns('user', array(
'password_salt'=>array(
'type'=>'CHAR',
'length'=>'36',
),
));
}

/**
* Rollback changes made in up().
*
* Drop the password_salt column from the user table.
*/
public function down()
{
$this->getDb()->dropColumns('user', array('password_salt'));
}
}
{code}

{code}
<?php

class Miscmod_Schema_04InitUserTablePasswordSalt extends Zend_Db_Schema_Migrate_Step
{
/**
* Assign a default password to all the users in the user table.
*
*/
public function up()
{
$this->getDb()->update('user', array('password'=>MD5('changeme')), 'password = '.$this->getDb()->quote(''));
}

/**
* Rollback changes made in up().
*
* Don't roll this change back.
*/
public function down()
{
// do nothing
}
}
{code}

{code}
<?php

class Miscmod_Schema_AddUserTableColumnRealName extends Zend_Db_Schema_Migrate_Step
{
protected $_step = 5;

/**
* Add a 'real_name' columnto the user table.
*
*/
public function up()
{
$this->getDb()->addColumns('user', array(
'real_name'=>array(
'type'=>'CHAR',
'length'=>'36',
),
));
}

/**
* Rollback changes made in up().
*
* Drop the real_name column from the user table.
*/
public function down()
{
$this->getDb()->dropColumns('user', array('real_name'));
}
}
{code}

||UC-02||
A project that requires developers and DBMs to modify the same database, but the DBMs are contractors and don't need to (or want to) know how the code works. The developers create database updates that effect their internal program data using PHP class extensions. A directory is setup for the DBMs to add changes in Zend_Config compatible files. Both parties can make their changes independently of each other while still maintaining an integrated sequence of changes to the same database(s).

{code}
<?php

class Miscmod_Schema_AddUserTableColumnPassword extends Zend_Db_Schema_Migrate_Step
{

/**
* Time stamp that this step was created formatted as 'yyyy-mm-dd-hh-mm-ss'
*
* @var string
*/
protected $_timestamp = '2009-06-05-13-55-45';

/**
* Add password column to the user table.
*
*/
public function up()
{
$this->getDb()->addColumns('internal_xref_foo', array(
'bar_id'=>array(
'type'=>'INTEGER',
),
));
}

/**
* Rollback changes made in up().
*
* Drop the password column from the user table.
*/
public function down()
{
$this->getDb()->dropColumns('internal_xref_foo', array('bar_id'));
}
}
{code}

{code}
<?xml version="1.0"?>
<migrate timestamp="2009-06-05-14-21-23">
<up>
<createtable>
<users>
<columns>
<user_uuid>
<type>CHAR</type>
<length>36</length>
<primary>true</primary>
</user_id>
<username>
<type>VARCHAR</type>
<length>64</length>
</username>
</columns>
</users>
</createtable>
</up>
<down>
<droptable>users</droptable>
</down>
</migrate>
{code}


||UC-03||
Create new PHP migration step files.
{code}
<?php
Zend_Db_Schema_Makestep::factory(array(
'type'=>'php',
'path'=>'/mnt/dev/project/application/module/mymod/schema/',
'name'=>'AddUserTable',
'classPrefix'=>'MyMod_Schema_',
'timestamp'=>time(),
));
{code}

Or the following, which would create the xml in UC-02
{code}
<?php
$options = array(
'type'=>'config',
'path'=>realpath('../../mymod/schema').'/createUserTable.xml',
'name'=>'AddUserTableColumnPasswordSalt',
'sortMethod'=>'timestamp',
);

$stepConfig = array(
'up'=>array(
'createtable'=>array(
'users'=>array(
'columns'=>array(
'user_uuid'=>array(
'type'=>'CHAR',
'length'=>'36',
'primary'=>true,
),
'username'=>array(
'type'=>'VARCHAR',
'length'=>'64',
),
),
),
),
),
'down'=>array(
'droptable'=>array('users'),
),
);

Zend_Db_Schema_Makestep::create($options, $stepConfig);
{code}

||UC-04||
Run migration from current step up (retrieved from the schema storage) up to the latest available step
{code}
<?php
. . .

// retrieve the $db object that we conveniently set up in bootstrap
$db = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('Db');

// create the schema manager instance, setting our DB as both the subject to be changed and as the schema status storage location.
$schemaManager = new Zend_Db_Schema_Manager($db, $db);

// run migrations up to latest available step.
$schemaManager->run();

. . .
{code}
{zone-data}

{zone-data:skeletons}
{code}
class Zend_Db_Schema_Exception extends Zend_Db_Exception {}

class Zend_Db_Schema_Manager
{
/* How to a handle data operation */
const DATA_MODE_RELOAD = 'reload'; /* Clear data and reload */
const DATA_MODE_ADD = 'add'; /* Add data but don't update existing */
const DATA_MODE_UPDATE = 'update'; /* Add data and update existing */

/* Migration direction */
const MIGRATE_UP = 'up'; /* Migrate Up */
const MIGRATE_DOWN = 'down'; /* Migrate Down */

/**
* Db that will be oeprated against.
*
* @var Zend_Db_Schema_Adapter_Abstract
*/
protected $_db;

/**
* Db Schema status data storage object.
*
* @var Zend_Db_Schema_Storage_Abstract
*/
protected $_storage;

/**
* Object providing the migration steps.
*
* @var Zend_Db_Schema_Source_Abstract
*/
protected $_source;

/**
* Each of the construction params can either be an object of the class
* specified or a value/object that will produce the correct object when
* passed to the respective factory.
*
* @param mixed $db
* @param mixed $storage
* @param mixed $source
*
* @return
*/
function __construct($db, $storage, $source)

/**
* Repopulates the _storage from the _source object's data. The optional $dataMode parameter
* may be used to control if existing entries are deleted before the update (i.e. 'reload'),
* if records in storage are updated while adding any new records (i.e. 'update'), or if only
* new records are added without updating or deleting anything else (i.e. 'add').
*
* @param string $dataMode
*/
public function repopulateStorage($dataMode = self::DATA_MODE_ADD)

/**
* Retrieve the Db Schema Adaptor object.
*
* @return Zend_Db_Schema_Adapter_Abstract
*/
public function getStorage()

/**
* Retrieve the Db Schema Storage object.
*
* @return Zend_Db_Schema_Storage_Abstract
*/
public function getStorage()

/**
* Retrieve the Db Schema Source object.
*
* @return Zend_Db_Schema_Source_Abstract
*/
public function getStorage()

/**
* Retrieve the sort value of the current step (i.e. either the integer 'step' or the timestamp.
*
* @return int
*/
public function getCurrentSchemaStep()

/**
* Run the migrations in _source.
*
* If $toStep is null, then migrations will run to the most recently available one (unless _storage reports it is allready updated to that step).
*
* If $direction is left null, then the direction of the migration is determined by comparing the current step to $toStep.
*
* If $fromStep is left null, then the current step reported by _storage is used.
*
* @param int $toStep optional
* @param string $direction optional
* @param int $fromStep optional
*
* @return void
*/
public function run($toStep = null, $direction = null, $fromStep = null)
}

class Zend_Db_Schema_Replicate
{
/**
* Where data will be sent to
*
* @var Zend_Db_Schema_Adapter_Abstract|file path|unset
*/
protected $_destination;

/**
* Where data will come from
*
* @var Zend_Db_Schema_Adapter_Abstract|Zend_Db_Adapter_Abstract|string (file path)
*/
protected $_source;

/**
* Internal storage of database schema
*/
protected $_schema;

/**
* One must designate a $source for the replication, but this does not need
* to be done in the constructor. This will likely be a database, but may
* also be a Zend_Db_Schema_Migrate_Abstract class or a Zend_Config compatible
* compatible file (e.g. 'ini', 'xml', etc.).
*
* The destination may optionally be set.
*
* @param Zend_Db_Schema_Adapter_Abstract|Zend_Db_Adapter_Abstract|string (file path) $source
* @param Zend_Db_Schema_Adapter_Abstract|file path $destination optional
*
* @return void
*/
function __construct($source = null, $destination = null)

/**
* Executes the replication, copying from the $source to the $destination. If destination is not set,
* then the source is just copied to an internal buffer. If $source is not passed in and has not been
* set on this object, then an error is thrown.
*
* @param Zend_Db_Schema_Adapter_Abstract|Zend_Db_Adapter_Abstract|string (file path) $source
* @param Zend_Db_Schema_Adapter_Abstract|file path $destination optional
*
* @return void
*/
function run($source = null, $destination = null)

/**
* Writes from the internal buffer to the $destination. If $destination is not passed in and has not been
* not been set on this object, then an error is thrown.
*
* @param Zend_Db_Schema_Adapter_Abstract|file path $destination optional
*
* @return void
*/
function write($destination = null)

/**
* Executes the replication, copying from the source into an internal buffer. If $source is not
* passed in and has not been set on this object, then an error is thrown.
*
* @param Zend_Db_Schema_Adapter_Abstract|Zend_Db_Adapter_Abstract|string (file path) $source
*
* @return void
*/
function read($source = null)

/**
* Set's the _source property. The $source must be of type Zend_Db_Schema_Adapter_Abstract,
* a string, an array, or a Zend_Config ojbect.
*
* @param Zend_Db_Schema_Adapter_Abstract|Zend_Config|string (file path) $source
*
* @return void
*/
function setSource($source)

/**
* Retrieves the _source property.
*
* @return mixed

*/
function getSource()

/**
* Set's the _destination property. The $source must be of type Zend_Db_Schema_Adapter_Abstract,
* or a string specifying a path to a file. The type of file to write is intuited from the
* extension on the file name.
*
* @param Zend_Db_Schema_Adapter_Abstract|Zend_Config|string (file path) $destination
*
* @return void
*/
function setDestination($destination)

/**
* Retrieves the _destination property.
*
* @return mixed

*/
function getDestination()
}

class Zend_Db_Schema_Adapter
{
/**
* Factory for Zend_Db_Adapter_Abstract classes.
*
* First argument may be of type Zend_Db_Adapter_Abstract, and use used to
* build the corresponding Zend_Db_Schema_Adapter, e.g. passing in an object
* of type Zend_Db_Adapter_Mysqli.
*
* First argument may be a string containing the base of the adapter class
* name, e.g. 'Mysqli' corresponds to class Zend_Db_Adapter_Mysqli. This
* is case-insensitive.
*
* First argument may alternatively be an object of type Zend_Config.
* The adapter class base name is read from the 'adapter' property.
* The adapter config parameters are read from the 'params' property.
*
* Second argument is optional and may be an associative array of key-value
* pairs. This is used as the argument to the adapter constructor.
*
* If the first argument is of type Zend_Db_Adapter_Abstract, it is used
* as is, and the second argument is ignored. If the first argument is of
* type Zend_Config, it is assumed to contain all parameters, and the
* second argument is ignored.
*
* @param mixed $adapter Zend_Db_Adapter_Abstract object, or string name of base adapter class, or Zend_Config object.
* @param mixed $config OPTIONAL; an array or Zend_Config object with adapter parameters.
* @return Zend_Db_Schema_Adapter_Abstract
* @throws Zend_Db_Schema_Exception
*/
public static function factory($adapter, $suppressErrors = true)

<?php

abstract class Zend_Db_Schema_Adapter_Abstract extends Zend_Db_Schema_MessengerInterface
{
/**
* This arra must be implemented by child classes, mapping the column type of
* individual databases to the the generic standard used by Zend DB Schema for
* definition perposes.
*
* @var array
*/
protected $_columnTypeMap = array(
'INTEGER'=>'some custom integer type',
'VARCHAR'=>'some custom varchar type',
'TEXT'=>'some custom text type',
);

/**
* @var Zend_Db_Adapter_Abstract
*/
protected $_db;

/**
* @var array cache for the table names in this database
*/
protected $_tables = false;

/**
* The $db is extended with the abstract classes of this class, but this class
* may be called as though it were the original Zend_Db_Adapter_Abstract (i.e.
* all calls are passed throug to it).
*
* @param Zend_Db_Adapter_Abstract $db
* @param bool $suppressErrors
*/
function __construct(Zend_Db_Adapter_Abstract $db, $suppressErrors = true)
{
$this->_db = $db;
parent::__construct($suppressErrors);
}

/**
* Add a table to this database.
*
* @param string $name name of the table to create
* @param array $columns columns to be added to this table
*
* @return Zend_Db_Schema_Adapter_Db2 Provides a fluent interface
*/
public abstract function createTable($name, array $columns);

/**
* Drop table from this database.
*
* @param string $name name of the table to drop
*
* @return Zend_Db_Schema_Adapter_Db2 Provides a fluent interface
*/
public abstract function dropTable($tableName);

/**
* Converts column type from generic to the specific required by this database.
*
* @param string $genericType type of column to add to the database
* @param string $requestedlength optional lengh of data type (if applicable)
*
* @return string
*/
//todo: can this be had from the underlying database object allready?!
public abstract function getType($genericType, $requestedlength = null);

/**
* Retrieve list of db tables from the underlying Zend_Db_Adapter
*
* @param bool $forceRefresh forces a requery of the database
*
* @return array
*/
public function getTables($forceRefresh = false)
{
if ($forceRefresh || !$this->_tables) {$this->_tables = $this->listTables();}
return $this->_tables;
}

/**
* Check to see if a table exists in this database.
*
* @param string $name name of the table to check for
*
* @return bool
*/
public function tableExists($name)
{
return in_array($name, $this->getTables());
}

/**
* call through to the underlying Zend_Db_Adapter
*
* @param string $method function name to call
* @param string $options array of arguments to pass
*
* @return mixed
*/
public function __call($method, array $options)

/**
* Convert a column type to/from the custom type used by the db a child class using
* $_columnTypeMap is the guide. If $fromGenericToCustom is set to false, then
* the value is converted from custom to generic.
*
* @param string $type Type name to convert.
* @param bool $fromGenericToCustom Direction to perform the conversion.
*
* @return mixed
*/
public function convertColumnType($type, $fromGenericToCustom = true)


}
class Zend_Db_Schema_Adapter_Exception extends Zend_Db_Schema_Exception{}

// example implementation:
class Zend_Db_Schema_Adapter_Mysql extends Zend_Db_Schema_Adapter_Abstract
{

protected $_columnTypeMap = array(
'INTEGER'=>'INTEGER',
'VARCHAR'=>'VARCHAR',
'TEXT'=>'TEXT',
);

/**
* Add a table to this database.
*
* @param string $name name of the table to create
* @param array $columns columns to be added to this table
*
* @return Zend_Db_Schema_Adapter_Mysql Provides a fluent interface
*/
function createTable($name, array $columns)

/**
* Drop table from this database.
*
* @param string $name name of the table to drop
*
* @return Zend_Db_Schema_Adapter_Mysql Provides a fluent interface
*/
function dropTable($name)
}

class Zend_Db_Schema_Makestep_Exception extends Zend_Db_Schema_Exception{}

class Zend_Db_Schema_Makestep
{
/**
* Factory. Type must be a valid Zend_Db_Schema_Makestep_Abstract
* implementation. The $options and $step parameters are passed to
* the construtor of the returned class.
*
* @param mixed $options
* @param array|null $step
* @return Zend_Db_Schema_Makestep_Abstract
*/
public static function factory($type, $options, $step = null)
}

abstract class Zend_Db_Schema_Makestep_Abstract
{

/**
* @var array Internal storage of step content
*/
private $_step = array(
'up'=>array(
),
'down'=>array(
),
);

/**
* Set the $options (see setOptions for details) and initial $step data as
* an array. This array must contain at least an 'up' and 'down' key in the
* lowest level, each of which is populate with the actions to write for this
* step (e.g. 'createtable', 'dropcolumn', etc.).
*
* @param mixed $options
* @param array|null $step
*/
function __construct($options = null, $step = null)

/**
* Each child class will have different options required in order to write to its
* particular target. This function provides the means for these options to be
* tested sanity and stored.
*
* @param mixed $options
*/
public abstract function setOptions($options);

/**
* Add actions to this step. If $downAction is not provided, then $upAction is
* checked for 'up' and 'down' keys. If both exist, then the array is integrated
* into the classes internal storage. If they do not exist, then the array is
* assumed to belong to 'up' and an attempt is made to guess at the 'down'
* action (e.g. for 'createtable', a complimentary 'droptable' is added).
*
* @param array $upAction
* @param array|null $downAction
*/
public function addActions($upAction, $downAction = null)

/**
* Retrieves the current set of action steps.
*
* @return array
*/
public function getActions()

/**
* Function to be implemented by child classes for writing the current $_step.
* Set $overwrite to false to prevent delete any existing same named step.
*
* @param bool $overwrite
*/
public abstract function write($overwrite = true);

/**
* Checks to see if the step as defined exists allready.
*
* @return bool
*/
public abstract function stepExists();


/**
* ref: http://us3.php.net/manual/en/function.array-merge-recursive.php#89684
* ref: http://us3.php.net/manual/en/function.array-merge-recursive.php#91049
* array_merge_recursive does indeed merge arrays, but it converts values with duplicate
* keys to arrays rather than overwriting the value in the first array with the duplicate
* value in the second array, as array_merge does. I.e., with array_merge_recursive,
* this happens (documented behavior):
*
* array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => array('org value', 'new value'));
*
* _mergeRecursiveDistinct does not change the datatypes of the values in the arrays.
* Matching keys' values in the second array overwrite those in the first array, as is the
* case with array_merge, i.e.:
*
* _mergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => array('new value'));
*
* Parameters are passed by reference, though only for performance reasons. They're not
* altered by this function.
*
* @param array $first
* @param array $second [$third, $forth, $fifth, etc.]
* @return array
*/
private function &_mergeRecursiveDistinct()
{
$aArrays = func_get_args();
$aMerged = $aArrays[0];

for($i = 1; $i < count($aArrays); $i++) {
if (is_array($aArrays[$i])) {
foreach ($aArrays[$i] as $key => $val) {
if (is_array($aArrays[$i][$key])) {
$aMerged[$key] = is_array($aMerged[$key]) ? $this->_mergeRecursiveDistinct($aMerged[$key], $aArrays[$i][$key]) : $aArrays[$i][$key];
} else {
$aMerged[$key] = $val;
}
}
}
}

return $aMerged;
}
}

{code}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>