This commit is contained in:
KhaiNguyen
2020-02-13 10:39:37 +07:00
commit 59401cb805
12867 changed files with 4646216 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
\define('YoastSEO_Vendor\\SQL_UNKNOWN_QUERY_TYPE', 1);
\define('YoastSEO_Vendor\\SQL_SELECT', 2);
\define('YoastSEO_Vendor\\SQL_INSERT', 4);
\define('YoastSEO_Vendor\\SQL_UPDATE', 8);
\define('YoastSEO_Vendor\\SQL_DELETE', 16);
\define('YoastSEO_Vendor\\SQL_ALTER', 32);
\define('YoastSEO_Vendor\\SQL_DROP', 64);
\define('YoastSEO_Vendor\\SQL_CREATE', 128);
\define('YoastSEO_Vendor\\SQL_SHOW', 256);
\define('YoastSEO_Vendor\\SQL_RENAME', 512);
\define('YoastSEO_Vendor\\SQL_SET', 1024);
/**
* Ruckusing_Adapter_Base
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_Base
{
/**
* dsn
*
* @var array
*/
private $_dsn;
/**
* db
*
*/
private $_db;
/**
* connection to db
*
* @var object|mysqli
*/
protected $_conn;
/**
* logger
*
* @var Ruckusing_Util_Logger
*/
public $logger;
/**
* Creates an instance of Ruckusing_Adapter_Base
*
* @param array $dsn The current dsn
*
* @return Ruckusing_Adapter_Base
*/
public function __construct($dsn)
{
$this->set_dsn($dsn);
}
/**
* Set a dsn
*
* @param object $dsn The current dsn
*/
public function set_dsn($dsn)
{
$this->_dsn = $dsn;
}
/**
* Get the current dsn
*
* @return array
*/
public function get_dsn()
{
return $this->_dsn;
}
/**
* Set a db
*
* @param array $db The current db
*/
public function set_db($db)
{
$this->_db = $db;
}
/**
* Get the current db
*
* @return array
*/
public function get_db()
{
return $this->_db;
}
/**
* Set a logger
*
* @param Ruckusing_Util_Logger $logger The current logger
* @throws Ruckusing_Exception
*/
public function set_logger($logger)
{
if (!$logger instanceof \YoastSEO_Vendor\Ruckusing_Util_Logger) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Logger parameter must be instance of Ruckusing_Util_Logger', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$this->logger = $logger;
}
/**
* Get the current logger
*
* @param $logger
* @return Ruckusing_Util_Logger
*/
public function get_logger($logger)
{
return $this->logger;
}
/**
* Check table exists
*
* @param string $tbl the table name
*
* @return boolean
*/
public function has_table($tbl)
{
return $this->table_exists($tbl);
}
/**
* Allows to override hardcoded schema table name constant in case of parallel migrations.
*
* @return string
*/
public function get_schema_version_table_name()
{
if (isset($this->_dsn['schema_version_table_name'])) {
return $this->_dsn['schema_version_table_name'];
}
return \YoastSEO_Vendor\RUCKUSING_TS_SCHEMA_TBL_NAME;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_ColumnDefinition
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_ColumnDefinition
{
/**
* adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter;
/**
* name
*
* @var string
*/
public $name;
/**
* type
*
* @var mixed
*/
public $type;
/**
* properties
*
* @var mixed
*/
public $properties;
/**
* options
*
* @var array
*/
private $_options = array();
/**
* Creates an instance of Ruckusing_Adapter_ColumnDefinition
*
* @param Ruckusing_Adapter_Base $adapter The current adapter
* @param string $name the name of the column
* @param string $type the type of the column
* @param array $options the column options
*
* @return Ruckusing_Adapter_ColumnDefinition
*/
public function __construct($adapter, $name, $type, $options = array())
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Invalid Adapter instance.', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
if (empty($name) || !\is_string($name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid 'name' parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($type) || !\is_string($type)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid 'type' parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$this->_adapter = $adapter;
$this->name = $name;
$this->type = $type;
$this->_options = $options;
}
/**
* sql version
*
* @return string
*/
public function to_sql()
{
$column_sql = \sprintf("%s %s", $this->_adapter->identifier($this->name), $this->sql_type());
$column_sql .= $this->_adapter->add_column_options($this->type, $this->_options);
return $column_sql;
}
/**
* sql string version
*
* @return string
*/
public function __toString()
{
return $this->to_sql();
}
/**
* sql version
*
* @return string
*/
private function sql_type()
{
return $this->_adapter->type_to_sql($this->type, $this->_options);
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_Interface
*
* Interface of adapters
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
interface Ruckusing_Adapter_Interface
{
/**
* get the current database name
*
* @return string
*/
public function get_database_name();
/**
* Quote a raw string.
*
* @param string $value Raw string
* @param string $column the column name
*
* @return string
*/
public function quote($value, $column = null);
/**
* supports migrations ?
*
* @return boolean
*/
public function supports_migrations();
/**
* native database types
*
* @return array
*/
public function native_database_types();
/**
* schema
*
* @return void
*/
public function schema($output_file);
/**
* execute
*
* @param string $query Query SQL
*
* @return void
*/
public function execute($query);
/**
* Quote a raw string.
*
* @param string $str Raw string
*
* @return string
*/
public function quote_string($str);
//database level operations
/**
* database exists
*
* @param string $db The database name
*
* @return boolean
*/
public function database_exists($db);
/**
* create table
*
* @param string $table_name The table name
* @param array $options Options for definition table
*
* @return boolean
*/
public function create_table($table_name, $options = array());
/**
* drop database
*
* @param string $db The database name
*
* @return boolean
*/
public function drop_database($db);
//table level opertions
/**
* table exists ?
*
* @param string $tbl Table name
*
* @return boolean
*/
public function table_exists($tbl);
/**
* drop table
*
* @param string $tbl The table name
*
* @return boolean
*/
public function drop_table($tbl);
/**
* rename table
*
* @param string $name The old name of table
* @param string $new_name The new name
*
* @return boolean
*/
public function rename_table($name, $new_name);
//column level operations
/**
* rename column
*
* @param string $table_name The table name where is the column
* @param string $column_name The old column name
* @param string $new_column_name The new column name
*
* @return boolean
*/
public function rename_column($table_name, $column_name, $new_column_name);
/**
* add column
*
* @param string $table_name The table name
* @param string $column_name The column name
* @param string $type The type generic of the column
* @param array $options The options definition of the column
*
* @return boolean
*/
public function add_column($table_name, $column_name, $type, $options = array());
/**
* remove column
*
* @param string $table_name The table name
* @param string $column_name The column name
*
* @return boolean
*/
public function remove_column($table_name, $column_name);
/**
* change column
*
* @param string $table_name The table name
* @param string $column_name The column name
* @param string $type The type generic of the column
* @param array $options The options definition of the column
*
* @return void
*/
public function change_column($table_name, $column_name, $type, $options = array());
/**
* remove index
*
* @param string $table_name The table name
* @param string $column_name The column name
*
* @return boolean
*/
public function remove_index($table_name, $column_name);
/**
* add index
*
* @param string $table_name The table name
* @param string $column_name The column name
* @param array $options The options definition of the index
*
* @return boolean
*/
public function add_index($table_name, $column_name, $options = array());
/**
* add timestamps
*
* @param string $table_name The table name
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
* @return boolean
*/
public function add_timestamps($table_name, $created_column_name, $updated_column_name);
/**
* remove timestamps
*
* @param string $table_name The table name
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
* @return boolean
*/
public function remove_timestamps($table_name, $created_column_name, $updated_column_name);
/**
* Wrapper to execute a query
*
* @param string $query query to run
*
* @return boolean
*/
public function query($query);
/**
* Wrapper to execute multiple queries
*
* @param string $queries queries to run
*
* @return boolean
*/
public function multi_query($queries);
}

View File

@@ -0,0 +1,268 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage MySQL
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_MySQL_TableDefinition
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage MySQL
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_MySQL_TableDefinition
{
/**
* adapter MySQL
*
* @var Ruckusing_Adapter_Mysql_Base
*/
private $_adapter;
/**
* Name
*
* @var string
*/
private $_name;
/**
* options
*
* @var array
*/
private $_options;
/**
* sql
*
* @var string
*/
private $_sql = "";
/**
* initialized
*
* @var boolean
*/
private $_initialized = \false;
/**
* Columns
*
* @var array
*/
private $_columns = array();
/**
* Table definition
*
* @var array
*/
private $_table_def;
/**
* primary keys
*
* @var array
*/
private $_primary_keys = array();
/**
* auto generate id
*
* @var boolean
*/
private $_auto_generate_id = \true;
/**
* Creates an instance of Ruckusing_Adapters_MySQL_Adapter
*
* @param Ruckusing_Adapter_MySQL_Base $adapter the current adapter
* @param string $name the table name
* @param array $options the options
*
* @throws Ruckusing_Exception
* @return Ruckusing_Adapter_MySQL_TableDefinition
*/
public function __construct($adapter, $name, $options = array())
{
//sanity check
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_MySQL_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid MySQL Adapter instance.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
if (!$name) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid 'name' parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$this->_adapter = $adapter;
$this->_name = $name;
$this->_options = $options;
$this->init_sql($name, $options);
$this->_table_def = new \YoastSEO_Vendor\Ruckusing_Adapter_TableDefinition($this->_adapter, $this->_options);
if (\array_key_exists('id', $options)) {
if (\is_bool($options['id']) && $options['id'] == \false) {
$this->_auto_generate_id = \false;
}
//if its a string then we want to auto-generate an integer-based
//primary key with this name
if (\is_string($options['id'])) {
$this->_auto_generate_id = \true;
$this->_primary_keys[] = $options['id'];
}
}
}
/*
public function primary_key($name, $auto_increment)
{
$options = array('auto_increment' => $auto_increment);
$this->column($name, "primary_key", $options);
}
*/
/**
* Create a column
*
* @param string $column_name the column name
* @param string $type the column type
* @param array $options
*/
public function column($column_name, $type, $options = array())
{
//if there is already a column by the same name then silently fail
//and continue
if ($this->_table_def->included($column_name) == \true) {
return;
}
$column_options = array();
if (\array_key_exists('primary_key', $options)) {
if ($options['primary_key'] == \true) {
$this->_primary_keys[] = $column_name;
}
}
if (\array_key_exists('auto_increment', $options)) {
if ($options['auto_increment'] == \true) {
$column_options['auto_increment'] = \true;
}
}
$column_options = \array_merge($column_options, $options);
$column = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, $column_name, $type, $column_options);
$this->_columns[] = $column;
}
//column
/**
* Shortcut to create timestamps columns (default created_at, updated_at)
*
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
*/
public function timestamps($created_column_name = "created_at", $updated_column_name = "updated_at")
{
$this->column($created_column_name, "datetime");
$this->column($updated_column_name, "timestamp", array("null" => \false, 'default' => 'CURRENT_TIMESTAMP', 'extra' => 'ON UPDATE CURRENT_TIMESTAMP'));
}
/**
* Get all primary keys
*
* @return string
*/
private function keys()
{
if (\count($this->_primary_keys) > 0) {
$lead = ' PRIMARY KEY (';
$quoted = array();
foreach ($this->_primary_keys as $key) {
$quoted[] = \sprintf("%s", $this->_adapter->identifier($key));
}
$primary_key_sql = ",\n" . $lead . \implode(",", $quoted) . ")";
return $primary_key_sql;
} else {
return '';
}
}
/**
* Table definition
*
* @param boolean $wants_sql
*
* @throws Ruckusing_Exception
* @return boolean | string
*/
public function finish($wants_sql = \false)
{
if ($this->_initialized == \false) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Table Definition: '%s' has not been initialized", $this->_name), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_TABLE_DEFINITION);
}
$opt_str = '';
if (\is_array($this->_options) && \array_key_exists('options', $this->_options)) {
$opt_str = $this->_options['options'];
} else {
if (isset($this->_adapter->db_info['charset'])) {
$opt_str = " DEFAULT CHARSET=" . $this->_adapter->db_info['charset'];
} else {
$opt_str = " DEFAULT CHARSET=utf8";
}
}
$close_sql = \sprintf(") %s;", $opt_str);
$create_table_sql = $this->_sql;
if ($this->_auto_generate_id === \true) {
$this->_primary_keys[] = 'id';
$primary_id = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, 'id', 'integer', array('unsigned' => \true, 'null' => \false, 'auto_increment' => \true));
$create_table_sql .= $primary_id->to_sql() . ",\n";
}
$create_table_sql .= $this->columns_to_str();
$create_table_sql .= $this->keys() . $close_sql;
if ($wants_sql) {
return $create_table_sql;
} else {
return $this->_adapter->execute_ddl($create_table_sql);
}
}
//finish
/**
* get all columns
*
* @return string
*/
private function columns_to_str()
{
$str = "";
$fields = array();
$len = \count($this->_columns);
for ($i = 0; $i < $len; $i++) {
$c = $this->_columns[$i];
$fields[] = $c->__toString();
}
return \join(",\n", $fields);
}
/**
* Init create sql
*
* @param string $name
* @param array $options
* @throws Exception
* @throws Ruckusing_Exception
*/
private function init_sql($name, $options)
{
//are we forcing table creation? If so, drop it first
if (\array_key_exists('force', $options) && $options['force'] == \true) {
try {
$this->_adapter->drop_table($name);
} catch (\YoastSEO_Vendor\Ruckusing_Exception $e) {
if ($e->getCode() != \YoastSEO_Vendor\Ruckusing_Exception::MISSING_TABLE) {
throw $e;
}
//do nothing
}
}
$temp = "";
if (\array_key_exists('temporary', $options)) {
$temp = " TEMPORARY";
}
$create_sql = \sprintf("CREATE%s TABLE ", $temp);
$create_sql .= \sprintf("%s (\n", $this->_adapter->identifier($name));
$this->_sql .= $create_sql;
$this->_initialized = \true;
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage PgSQL
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_PgSQL_TableDefinition
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage PgSQL
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_PgSQL_TableDefinition extends \YoastSEO_Vendor\Ruckusing_Adapter_TableDefinition
{
/**
* adapter PgSQL
*
* @var Ruckusing_Adapter_Pgsql_Base
*/
private $_adapter;
/**
* Name
*
* @var string
*/
private $_name;
/**
* options
*
* @var array
*/
private $_options;
/**
* sql
*
* @var string
*/
private $_sql = "";
/**
* initialized
*
* @var boolean
*/
private $_initialized = \false;
/**
* Columns
*
* @var array
*/
private $_columns = array();
/**
* Table definition
*
* @var array
*/
private $_table_def;
/**
* primary keys
*
* @var array
*/
private $_primary_keys = array();
/**
* auto generate id
*
* @var boolean
*/
private $_auto_generate_id = \true;
/**
* Creates an instance of Ruckusing_PostgresTableDefinition
*
* @param Ruckusing_Adapter_PgSQL_Base $adapter the current adapter
* @param string $name the table name
* @param array $options
*
* @throws Ruckusing_Exception
* @return \Ruckusing_Adapter_PgSQL_TableDefinition
*/
public function __construct($adapter, $name, $options = array())
{
//sanity check
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_PgSQL_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid Postgres Adapter instance.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
if (!$name) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid 'name' parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$this->_adapter = $adapter;
$this->_name = $name;
$this->_options = $options;
$this->init_sql($name, $options);
$this->_table_def = new \YoastSEO_Vendor\Ruckusing_Adapter_TableDefinition($this->_adapter, $this->_options);
if (\array_key_exists('id', $options)) {
if (\is_bool($options['id']) && $options['id'] == \false) {
$this->_auto_generate_id = \false;
}
//if its a string then we want to auto-generate an integer-based primary key with this name
if (\is_string($options['id'])) {
$this->_auto_generate_id = \true;
$this->_primary_keys[] = $options['id'];
}
}
}
/**
* Create a column
*
* @param string $column_name the column name
* @param string $type the column type
* @param array $options
*/
public function column($column_name, $type, $options = array())
{
//if there is already a column by the same name then silently fail and continue
if ($this->_table_def->included($column_name) == \true) {
return;
}
$column_options = array();
if (\array_key_exists('primary_key', $options)) {
if ($options['primary_key'] == \true) {
$this->_primary_keys[] = $column_name;
}
}
/*
if (array_key_exists('auto_increment', $options)) {
if ($options['auto_increment'] == true) {
$column_options['auto_increment'] = true;
}
}
*/
$column_options = \array_merge($column_options, $options);
$column = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, $column_name, $type, $column_options);
$this->_columns[] = $column;
}
//column
/**
* Shortcut to create timestamps columns (default created_at, updated_at)
*
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
*/
public function timestamps($created_column_name = "created_at", $updated_column_name = "updated_at")
{
$this->column($created_column_name, "datetime", array("null" => \false));
$this->column($updated_column_name, "datetime", array("null" => \false));
}
/**
* Get all primary keys
*
* @return string
*/
private function keys()
{
if (\count($this->_primary_keys) > 0) {
$lead = ' PRIMARY KEY (';
$quoted = array();
foreach ($this->_primary_keys as $key) {
$quoted[] = \sprintf("%s", $this->_adapter->identifier($key));
}
$primary_key_sql = ",\n" . $lead . \implode(",", $quoted) . ")";
return $primary_key_sql;
} else {
return '';
}
}
/**
* Table definition
*
* @param boolean $wants_sql
*
* @throws Ruckusing_Exception
* @return boolean | string
*/
public function finish($wants_sql = \false)
{
if ($this->_initialized == \false) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Table Definition: '%s' has not been initialized", $this->_name), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_TABLE_DEFINITION);
}
if (\is_array($this->_options) && \array_key_exists('options', $this->_options)) {
$opt_str = $this->_options['options'];
} else {
$opt_str = null;
}
$close_sql = \sprintf(") %s;", $opt_str);
$create_table_sql = $this->_sql;
if ($this->_auto_generate_id === \true) {
$this->_primary_keys[] = 'id';
$primary_id = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, 'id', 'primary_key');
$create_table_sql .= $primary_id->to_sql() . ",\n";
}
$create_table_sql .= $this->columns_to_str();
$create_table_sql .= $this->keys() . $close_sql;
if ($wants_sql) {
return $create_table_sql;
} else {
return $this->_adapter->execute_ddl($create_table_sql);
}
}
/**
* get all columns
*
* @return string
*/
private function columns_to_str()
{
$str = "";
$fields = array();
$len = \count($this->_columns);
for ($i = 0; $i < $len; $i++) {
$c = $this->_columns[$i];
$fields[] = $c->__toString();
}
return \join(",\n", $fields);
}
/**
* Init create sql
*
* @param string $name
* @param array $options
* @throws Exception
* @throws Ruckusing_Exception
*/
private function init_sql($name, $options)
{
//are we forcing table creation? If so, drop it first
if (\array_key_exists('force', $options) && $options['force'] == \true) {
try {
$this->_adapter->drop_table($name);
} catch (\YoastSEO_Vendor\Ruckusing_Exception $e) {
if ($e->getCode() != \YoastSEO_Vendor\Ruckusing_Exception::MISSING_TABLE) {
throw $e;
}
//do nothing
}
}
$temp = "";
$create_sql = \sprintf("CREATE%s TABLE ", $temp);
$create_sql .= \sprintf("%s (\n", $this->_adapter->identifier($name));
$this->_sql .= $create_sql;
$this->_initialized = \true;
}
}

View File

@@ -0,0 +1,791 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage Sqlite3
* @author Andrzej Oczkowicz <andrzejoczkowicz % gmail . com>
* @author Piotr Olaszewski <piotroo89 % gmail dot com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
\define('YoastSEO_Vendor\\SQLITE3_MAX_IDENTIFIER_LENGTH', 64);
/**
* Ruckusing_Adapter_Sqlite3_Base
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage Sqlite3
* @author Piotr Olaszewski <piotroo89 % gmail dot com>
* @author Andrzej Oczkowicz <andrzejoczkowicz % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_Sqlite3_Base extends \YoastSEO_Vendor\Ruckusing_Adapter_Base implements \YoastSEO_Vendor\Ruckusing_Adapter_Interface
{
/**
* @var SQLite3
*/
private $sqlite3;
/**
* @var
*/
private $db_info;
/**
* @var bool
*/
private $_in_transaction;
/**
* @param array $dsn
* @param $logger
*/
public function __construct($dsn, $logger)
{
parent::__construct($dsn);
$this->connect($dsn);
$this->set_logger($logger);
$this->_in_transaction = \false;
}
/**
* @param $dsn
*/
private function connect($dsn)
{
$this->db_connect($dsn);
}
/**
* @param $dsn
* @return bool
* @throws Ruckusing_Exception
*/
private function db_connect($dsn)
{
if (!\class_exists('SQLite3')) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("\nIt appears you have not compiled PHP with SQLite3 support: missing class SQLite3", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
$db_info = $this->get_dsn();
if ($db_info) {
$this->db_info = $db_info;
try {
$this->sqlite3 = new \SQLite3($db_info['database']);
} catch (\Exception $e) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Could not connect to the DB, check database name.\nReason: " . $e->getMessage(), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG, $e);
}
return \true;
} else {
throw new \YoastSEO_Vendor\Ruckusing_Exception("\n\nCould not extract DB connection information from: {$dsn}\n\n", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
}
/**
*
*/
public function create_schema_version_table()
{
if (!$this->has_table($this->get_schema_version_table_name())) {
$t = $this->create_table($this->get_schema_version_table_name(), array('id' => \false));
$t->column('version', 'string');
$t->finish();
$this->add_index($this->get_schema_version_table_name(), 'version', array('unique' => \true));
}
}
/**
* @return mixed
*/
public function get_database_name()
{
return $this->db_info['database'];
}
/**
* @param $string
* @return string
*/
public function identifier($string)
{
return '"' . $string . '"';
}
/**
* @param string $value
* @param null $column
* @return string
*/
public function quote($value, $column = null)
{
return "'{$value}'";
}
/**
* @param string $query
* @return array|bool|int
*/
public function query($query)
{
$this->logger->log($query);
$query_type = $this->determine_query_type($query);
$data = array();
if ($query_type == \YoastSEO_Vendor\SQL_SELECT || $query_type == \YoastSEO_Vendor\SQL_SHOW) {
$SqliteResult = $this->executeQuery($query);
while ($row = $SqliteResult->fetchArray(\SQLITE3_ASSOC)) {
$data[] = $row;
}
return $data;
} else {
$this->executeQuery($query);
if ($query_type == \YoastSEO_Vendor\SQL_INSERT) {
return $this->sqlite3->lastInsertRowID();
}
return \true;
}
}
/**
* Execute several queries
*
* @param string $queries queries to run
*
* @throws Ruckusing_Exception
* @return boolean
*/
public function multi_query($queries)
{
$res = $this->sqlite3->exec($queries);
if ($this->isError($res)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error executing 'query' with:\n%s\n\nReason: %s\n\n", $queries, $this->lastErrorMsg()), \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
return \true;
}
/**
* @param $query
* @return SQLite3Result
* @throws Ruckusing_Exception
*/
private function executeQuery($query)
{
$SqliteResult = $this->sqlite3->query($query);
if ($this->isError($SqliteResult)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error executing 'query' with:\n%s\n\nReason: %s\n\n", $query, $this->lastErrorMsg()), \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
return $SqliteResult;
}
/**
*
*/
public function start_transaction()
{
if ($this->inTransaction() === \false) {
$this->beginTransaction();
}
}
/**
*
*/
public function commit_transaction()
{
if ($this->inTransaction()) {
$this->commit();
}
}
/**
*
*/
public function rollback_transaction()
{
if ($this->inTransaction()) {
$this->rollback();
}
}
/**
* @param $query
* @return int
*/
private function determine_query_type($query)
{
$query = \strtolower(\trim($query));
$match = array();
\preg_match('/^(\\w)*/i', $query, $match);
$type = $match[0];
switch ($type) {
case 'select':
return \YoastSEO_Vendor\SQL_SELECT;
case 'update':
return \YoastSEO_Vendor\SQL_UPDATE;
case 'delete':
return \YoastSEO_Vendor\SQL_DELETE;
case 'insert':
return \YoastSEO_Vendor\SQL_INSERT;
case 'alter':
return \YoastSEO_Vendor\SQL_ALTER;
case 'drop':
return \YoastSEO_Vendor\SQL_DROP;
case 'create':
return \YoastSEO_Vendor\SQL_CREATE;
case 'pragma':
return \YoastSEO_Vendor\SQL_SHOW;
default:
return \YoastSEO_Vendor\SQL_UNKNOWN_QUERY_TYPE;
}
}
/**
* @return bool
*/
public function supports_migrations()
{
return \true;
}
/**
* @return array
*/
public function native_database_types()
{
$types = array('primary_key' => array('name' => 'integer'), 'string' => array('name' => 'varchar', 'limit' => 255), 'text' => array('name' => 'text'), 'tinytext' => array('name' => 'text'), 'mediumtext' => array('name' => 'text'), 'integer' => array('name' => 'integer'), 'tinyinteger' => array('name' => 'smallint'), 'smallinteger' => array('name' => 'smallint'), 'mediuminteger' => array('name' => 'integer'), 'biginteger' => array('name' => 'bigint'), 'float' => array('name' => 'float'), 'decimal' => array('name' => 'decimal', 'scale' => 0, 'precision' => 10), 'datetime' => array('name' => 'datetime'), 'timestamp' => array('name' => 'datetime'), 'time' => array('name' => 'time'), 'date' => array('name' => 'date'), 'binary' => array('name' => 'blob'), 'tinybinary' => array('name' => "blob"), 'mediumbinary' => array('name' => "blob"), 'longbinary' => array('name' => "blob"), 'boolean' => array('name' => 'boolean'));
return $types;
}
/**
* @param $output_file
* @return string
*/
public function schema($output_file)
{
$command = \sprintf("sqlite3 '%s' .schema > '%s'", $this->db_info['database'], $output_file);
return \system($command);
}
/**
* @param $db
* @param array $options
* @return bool
*/
public function create_database($db, $options = array())
{
$this->log_unsupported_feature(__FUNCTION__);
return \true;
}
/**
* @param string $query
* @return array|bool|int|null
*/
public function execute($query)
{
return $this->query($query);
}
/**
* @param string $str
* @return string
*/
public function quote_string($str)
{
return $this->sqlite3->escapeString($str);
}
/**
* @param string $db
* @return bool
*/
public function database_exists($db)
{
$this->log_unsupported_feature(__FUNCTION__);
return \true;
}
/**
* @param string $table_name
* @param array $options
* @return Ruckusing_Adapter_Sqlite3_TableDefinition
*/
public function create_table($table_name, $options = array())
{
return new \YoastSEO_Vendor\Ruckusing_Adapter_Sqlite3_TableDefinition($this, $table_name, $options);
}
/**
* @param string $databaseName
* @return bool
*/
public function drop_database($databaseName)
{
$this->log_unsupported_feature(__FUNCTION__);
return \true;
}
/**
* @param $feature
*/
public function log_unsupported_feature($feature)
{
$this->logger->log(\sprintf("WARNING: Unsupported SQLite3 feature: %s", $feature));
}
/**
* @param string $tbl
* @param bool $reload_tables
* @return bool
*/
public function table_exists($tbl, $reload_tables = \false)
{
$query = \sprintf("SELECT tbl_name FROM sqlite_master WHERE type='table' AND tbl_name=%s;", $this->quote_column_name($tbl));
$table = $this->select_one($query);
return \is_array($table) && \sizeof($table) > 0;
}
/**
* @param string $table_name
* @return bool
*/
public function drop_table($table_name)
{
$ddl = \sprintf("DROP TABLE IF EXISTS %s", $this->quote_table_name($table_name));
$this->execute_ddl($ddl);
return \true;
}
/**
* @param $string
* @return string
*/
public function quote_table_name($string)
{
return '"' . $string . '"';
}
/**
* @param string $name
* @param string $new_name
* @return bool
* @throws Ruckusing_Exception
*/
public function rename_table($name, $new_name)
{
if (empty($name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing original column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($new_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing new column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$sql = \sprintf("ALTER TABLE %s RENAME TO %s", $this->identifier($name), $this->identifier($new_name));
return $this->execute_ddl($sql);
}
/**
* @param string $table_name
* @param string $column_name
* @param string $new_column_name
* @return bool
*/
public function rename_column($table_name, $column_name, $new_column_name)
{
$this->log_unsupported_feature(__FUNCTION__);
return \true;
}
/**
* @param $string
* @return string
*/
public function quote_column_name($string)
{
return '"' . $string . '"';
}
/**
* @param string $table_name
* @param string $column_name
* @param string $type
* @param array $options
* @return bool
* @throws Ruckusing_Exception
*/
public function add_column($table_name, $column_name, $type, $options = array())
{
if (empty($table_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing table name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($column_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($type)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing type parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$defaultOptions = array('limit' => null, 'precision' => null, 'scale' => null);
$options = \array_merge($defaultOptions, $options);
$sql = \sprintf("ALTER TABLE %s ADD COLUMN %s %s", $this->quote_table_name($table_name), $this->quote_column_name($column_name), $this->type_to_sql($type, $options));
$sql .= $this->add_column_options($type, $options);
return $this->execute_ddl($sql);
}
/**
* @param string $table_name
* @param string $column_name
*/
public function remove_column($table_name, $column_name)
{
$this->log_unsupported_feature(__FUNCTION__);
}
/**
* @param string $table_name
* @param string $column_name
* @param string $type
* @param array $options
*/
public function change_column($table_name, $column_name, $type, $options = array())
{
$this->log_unsupported_feature(__FUNCTION__);
}
/**
* @param string $table_name
* @param string $column_name
* @param array $options
* @return bool
* @throws Ruckusing_Exception
*/
public function remove_index($table_name, $column_name, $options = array())
{
if (empty($table_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing table name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($column_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
//did the user specify an index name?
if (\is_array($options) && \array_key_exists('name', $options)) {
$index_name = $options['name'];
} else {
$index_name = \YoastSEO_Vendor\Ruckusing_Util_Naming::index_name($table_name, $column_name);
}
$sql = \sprintf("DROP INDEX %s", $this->quote_column_name($index_name));
return $this->execute_ddl($sql);
}
/**
* @param string $table_name
* @param string $column_name
* @param array $options
* @return bool
* @throws Ruckusing_Exception
*/
public function add_index($table_name, $column_name, $options = array())
{
if (empty($table_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing table name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($column_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
//unique index?
if (\is_array($options) && \array_key_exists('unique', $options) && $options['unique'] === \true) {
$unique = \true;
} else {
$unique = \false;
}
//did the user specify an index name?
if (\is_array($options) && \array_key_exists('name', $options)) {
$index_name = $options['name'];
} else {
$index_name = \YoastSEO_Vendor\Ruckusing_Util_Naming::index_name($table_name, $column_name);
}
if (\strlen($index_name) > \YoastSEO_Vendor\SQLITE3_MAX_IDENTIFIER_LENGTH) {
$msg = "The auto-generated index name is too long for Postgres (max is 64 chars). ";
$msg .= "Considering using 'name' option parameter to specify a custom name for this index.";
$msg .= " Note: you will also need to specify";
$msg .= " this custom name in a drop_index() - if you have one.";
throw new \YoastSEO_Vendor\Ruckusing_Exception($msg, \YoastSEO_Vendor\Ruckusing_Exception::INVALID_INDEX_NAME);
}
if (!\is_array($column_name)) {
$column_names = array($column_name);
} else {
$column_names = $column_name;
}
$cols = array();
foreach ($column_names as $name) {
$cols[] = $this->quote_column_name($name);
}
$sql = \sprintf("CREATE %sINDEX %s ON %s(%s)", $unique ? "UNIQUE " : "", $this->quote_column_name($index_name), $this->quote_column_name($table_name), \join(", ", $cols));
return $this->execute_ddl($sql);
}
/**
* @param $table_name
* @param $created_column_name
* @param $updated_column_name
* @return boolean
*/
public function add_timestamps($table_name, $created_column_name, $updated_column_name)
{
$this->log_unsupported_feature(__FUNCTION__);
}
/**
* @param $table_name
* @param $created_column_name
* @param $updated_column_name
* @return boolean
*/
public function remove_timestamps($table_name, $created_column_name, $updated_column_name)
{
$this->log_unsupported_feature(__FUNCTION__);
}
/**
* @param $type
* @param $options
* @param bool $performing_change
* @return string
*/
public function add_column_options($type, $options, $performing_change = \false)
{
if (!\is_array($options)) {
return '';
}
$sql = "";
if (!$performing_change) {
if (\array_key_exists('default', $options) && $options['default'] !== null) {
if (\is_int($options['default'])) {
$default_format = '%d';
} elseif (\is_bool($options['default'])) {
$default_format = "'%d'";
} elseif ($options['default'] == 'CURRENT_TIMESTAMP') {
$default_format = "%s";
} else {
$default_format = "'%s'";
}
$default_value = \sprintf($default_format, $options['default']);
$sql .= \sprintf(" DEFAULT %s", $default_value);
}
if (\array_key_exists('null', $options) && $options['null'] === \false) {
$sql .= " NOT NULL";
}
if (\array_key_exists('extra', $options)) {
$sql .= \sprintf(" %s", $this->quote_string($options['extra']));
}
}
return $sql;
}
/**
* @param $type
* @param array $options
* @return string
* @throws Ruckusing_Exception
*/
public function type_to_sql($type, $options = array())
{
$natives = $this->native_database_types();
if (!\array_key_exists($type, $natives)) {
$error = \sprintf("Error: I don't know what column type of '%s' maps to for SQLite3.", $type);
$error .= "\nYou provided: {$type}\n";
$error .= "Valid types are: \n";
$error .= \implode(', ', \array_diff(\array_keys($natives), array('primary_key')));
throw new \YoastSEO_Vendor\Ruckusing_Exception($error, \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$native_type = $natives[$type];
$column_type_sql = $native_type['name'];
$optionsLimit = isset($options['limit']) ? $options['limit'] : null;
$nativeLimit = isset($native_type['limit']) ? $native_type['limit'] : null;
$limit = $optionsLimit ? $optionsLimit : $nativeLimit;
if ($limit !== null) {
$column_type_sql .= \sprintf("(%d)", $limit);
}
return $column_type_sql;
}
/**
* @param $table
* @param $column
* @return array|null
* @throws Ruckusing_Exception
*/
public function column_info($table, $column)
{
if (empty($table)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing table name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($column)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing original column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
try {
$pragmaTable = $this->query('pragma table_info(' . $table . ')');
$data = array();
$pragmaColumnInfo = $this->extract_column_info($pragmaTable, $column);
if (\is_array($pragmaColumnInfo)) {
$data['type'] = $pragmaColumnInfo['type'];
$data['name'] = $column;
$data['field'] = $column;
$data['null'] = $pragmaColumnInfo['notnull'] == 0;
$data['default'] = $pragmaColumnInfo['dflt_value'];
}
return $data;
} catch (\Exception $e) {
return null;
}
}
/**
* @param $pragmaTable
* @param $columnName
* @return null
*/
private function extract_column_info($pragmaTable, $columnName)
{
foreach ($pragmaTable as $columnInfo) {
if ($columnInfo['name'] == $columnName) {
return $columnInfo;
}
}
return null;
}
/**
* @param $ddl
* @return bool
*/
public function execute_ddl($ddl)
{
$this->query($ddl);
return \true;
}
/**
* @param $table_name
* @return array
*/
public function indexes($table_name)
{
$sql = \sprintf("PRAGMA INDEX_LIST(%s);", $this->quote_table_name($table_name));
$result = $this->select_all($sql);
$indexes = array();
foreach ($result as $row) {
$indexes[] = array('name' => $row['name'], 'unique' => $row['unique'] ? \true : \false);
}
return $indexes;
}
/**
* @param $SQLite3Result
* @return bool
*/
private function isError($SQLite3Result)
{
return $SQLite3Result === \FALSE;
}
/**
* @return string
*/
private function lastErrorMsg()
{
return $this->sqlite3->lastErrorMsg();
}
/**
* @param $table
* @return array
*/
public function primary_keys($table)
{
$result = $this->query('pragma table_info(' . $table . ')');
$primary_keys = array();
foreach ($result as $row) {
if ($row['pk']) {
$primary_keys[] = array('name' => $row['name'], 'type' => $row['type']);
}
}
return $primary_keys;
}
/**
* @param $query
* @return array
* @throws Ruckusing_Exception
*/
public function select_one($query)
{
$this->logger->log($query);
$query_type = $this->determine_query_type($query);
if ($query_type == \YoastSEO_Vendor\SQL_SELECT || $query_type == \YoastSEO_Vendor\SQL_SHOW) {
$res = $this->executeQuery($query);
if ($this->isError($res)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error executing 'query' with:\n%s\n\nReason: %s\n\n", $query, $this->lastErrorMsg()), \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
return $res->fetchArray(\SQLITE3_ASSOC);
} else {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Query for select_one() is not one of SELECT or SHOW: {$query}", \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
}
/**
* @param $query
* @return array|bool|int
*/
public function select_all($query)
{
return $this->query($query);
}
/**
* @param $column_name
* @param $type
* @param null $options
* @return string
*/
public function column_definition($column_name, $type, $options = null)
{
$col = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this, $column_name, $type, $options);
return $col->__toString();
}
/**
* @param $table_name
* @param $column_name
* @param array $options
* @return bool
* @throws Ruckusing_Exception
*/
public function has_index($table_name, $column_name, $options = array())
{
if (empty($table_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing table name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (empty($column_name)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Missing column name parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
if (\is_array($options) && \array_key_exists('name', $options)) {
$index_name = $options['name'];
} else {
$index_name = \YoastSEO_Vendor\Ruckusing_Util_Naming::index_name($table_name, $column_name);
}
$indexes = $this->indexes($table_name);
foreach ($indexes as $idx) {
if ($idx['name'] == $index_name) {
return \true;
}
}
return \false;
}
/**
* @param $version
* @return bool
*/
public function set_current_version($version)
{
$sql = \sprintf("INSERT INTO %s (version) VALUES ('%s')", $this->get_schema_version_table_name(), $version);
return $this->execute_ddl($sql);
}
/**
* @param $version
* @return bool
*/
public function remove_version($version)
{
$sql = \sprintf("DELETE FROM %s WHERE version = '%s'", $this->get_schema_version_table_name(), $version);
return $this->execute_ddl($sql);
}
/**
* @return bool
*/
private function inTransaction()
{
return $this->_in_transaction;
}
/**
* @throws Ruckusing_Exception
*/
private function beginTransaction()
{
if ($this->_in_transaction) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Transaction already started', \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
$this->execute_ddl("BEGIN");
$this->_in_transaction = \true;
}
/**
* @throws Ruckusing_Exception
*/
private function commit()
{
if ($this->_in_transaction === \false) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Transaction not started', \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
$this->execute_ddl("COMMIT");
$this->_in_transaction = \false;
}
/**
* @throws Ruckusing_Exception
*/
private function rollback()
{
if ($this->_in_transaction === \false) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Transaction not started', \YoastSEO_Vendor\Ruckusing_Exception::QUERY_ERROR);
}
$this->execute_ddl("ROLLBACK");
$this->_in_transaction = \false;
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage Sqlite3
* @author Piotr Olaszewski <piotroo89 % gmail dot com>
* @author Andrzej Oczkowicz <andrzejoczkowicz % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_Sqlite3_TableDefinition
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @subpackage Sqlite3
* @author Piotr Olaszewski <piotroo89 % gmail dot com>
* @author Andrzej Oczkowicz <andrzejoczkowicz % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_Sqlite3_TableDefinition extends \YoastSEO_Vendor\Ruckusing_Adapter_TableDefinition
{
/**
* @var Ruckusing_Adapter_Sqlite3_Base
*/
private $_adapter;
/**
* @var
*/
private $_name;
/**
* @var array
*/
private $_options;
/**
* @var string
*/
private $_sql = "";
/**
* @var bool
*/
private $_initialized = \false;
/**
* @var array
*/
private $_columns = array();
/**
* @var Ruckusing_Adapter_TableDefinition
*/
private $_table_def;
/**
* @var array
*/
private $_primary_keys = array();
/**
* @var bool
*/
private $_auto_generate_id = \true;
/**
* @param Ruckusing_Adapter_Base $adapter
* @param $name
* @param array $options
* @throws Ruckusing_Exception
*/
public function __construct($adapter, $name, $options = array())
{
//sanity check
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Sqlite3_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid Postgres Adapter instance.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
if (!$name) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Invalid 'name' parameter", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$this->_adapter = $adapter;
$this->_name = $name;
$this->_options = $options;
$this->init_sql($name, $options);
$this->_table_def = new \YoastSEO_Vendor\Ruckusing_Adapter_TableDefinition($this->_adapter, $this->_options);
if (\array_key_exists('id', $options)) {
if (\is_bool($options['id']) && $options['id'] == \false) {
$this->_auto_generate_id = \false;
}
//if its a string then we want to auto-generate an integer-based primary key with this name
if (\is_string($options['id'])) {
$this->_auto_generate_id = \true;
$this->_primary_keys[] = $options['id'];
}
}
}
/**
* @param $name
* @param $options
* @throws Exception
* @throws Ruckusing_Exception
*/
private function init_sql($name, $options)
{
//are we forcing table creation? If so, drop it first
if (\array_key_exists('force', $options) && $options['force']) {
try {
$this->_adapter->drop_table($name);
} catch (\YoastSEO_Vendor\Ruckusing_Exception $e) {
if ($e->getCode() != \YoastSEO_Vendor\Ruckusing_Exception::MISSING_TABLE) {
throw $e;
}
//do nothing
}
}
$temp = "";
$create_sql = \sprintf("CREATE%s TABLE ", $temp);
$create_sql .= \sprintf("%s (\n", $this->_adapter->identifier($name));
$this->_sql .= $create_sql;
$this->_initialized = \true;
}
/**
* @param $column_name
* @param $type
* @param array $options
*/
public function column($column_name, $type, $options = array())
{
//if there is already a column by the same name then silently fail and continue
if ($this->_table_def->included($column_name)) {
return;
}
$column_options = array();
if (\array_key_exists('primary_key', $options)) {
if ($options['primary_key'] == \true) {
$this->_primary_keys[] = $column_name;
}
}
$column_options = \array_merge($column_options, $options);
$column = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, $column_name, $type, $column_options);
$this->_columns[] = $column;
}
/**
* @param bool $wants_sql
* @return bool|string
* @throws Ruckusing_Exception
*/
public function finish($wants_sql = \false)
{
if (!$this->_initialized) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Table Definition: '%s' has not been initialized", $this->_name), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_TABLE_DEFINITION);
}
if (\is_array($this->_options) && \array_key_exists('options', $this->_options)) {
$opt_str = $this->_options['options'];
} else {
$opt_str = null;
}
$close_sql = \sprintf(") %s;", $opt_str);
$create_table_sql = $this->_sql;
if ($this->_auto_generate_id === \true) {
$this->_primary_keys[] = 'id';
$primary_id = new \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition($this->_adapter, 'id', 'primary_key');
$create_table_sql .= $primary_id->to_sql() . ",\n";
}
$create_table_sql .= $this->columns_to_str();
$create_table_sql .= $this->keys() . $close_sql;
if ($wants_sql) {
return $create_table_sql;
} else {
return $this->_adapter->execute_ddl($create_table_sql);
}
}
/**
* @return string
*/
private function columns_to_str()
{
$fields = array();
$len = \count($this->_columns);
for ($i = 0; $i < $len; $i++) {
$c = $this->_columns[$i];
$fields[] = $c->__toString();
}
return \join(",\n", $fields);
}
/**
*
*/
public function timestamps($created_column_name = "created_at", $updated_column_name = "updated_at")
{
$this->column($created_column_name, "datetime", array("null" => \false, 'default' => 'CURRENT_TIMESTAMP'));
$this->column($updated_column_name, "datetime", array("null" => \false, 'default' => 'CURRENT_TIMESTAMP'));
}
/**
* @return string
*/
private function keys()
{
if (\count($this->_primary_keys) > 0) {
$lead = ' PRIMARY KEY (';
$quoted = array();
foreach ($this->_primary_keys as $key) {
$quoted[] = \sprintf("%s", $this->_adapter->identifier($key));
}
$primary_key_sql = ",\n" . $lead . \implode(",", $quoted) . ")";
return $primary_key_sql;
} else {
return '';
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Adapter_TableDefinition
*
* @category Ruckusing
* @package Ruckusing_Adapter
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Adapter_TableDefinition
{
/**
* columns
*
* @var array
*/
private $_columns = array();
/**
* adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter;
/**
* Creates an instance of Ruckusing_Adapter_TableDefinition
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return Ruckusing_Adapter_TableDefinition
*/
public function __construct($adapter)
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Invalid Adapter instance.', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
$this->_adapter = $adapter;
}
/**
* __call
*
* @param string $name The method name
* @param array $args The parameters of method called
*
* @throws Ruckusing_Exception
*/
public function __call($name, $args)
{
throw new \YoastSEO_Vendor\Ruckusing_Exception('Method unknown (' . $name . ')', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_MIGRATION_METHOD);
}
/**
* Determine whether or not the given column already exists in our
* table definition.
*
* This method is lax enough that it can take either a string column name
* or a Ruckusing_Adapter_ColumnDefinition object.
*
* @param string $_column the name of the column
*
* @return boolean
*/
public function included($column)
{
$k = \count($this->_columns);
for ($i = 0; $i < $k; $i++) {
$col = $this->_columns[$i];
if (\is_string($column) && $col->name == $column) {
return \true;
}
if ($column instanceof \YoastSEO_Vendor\Ruckusing_Adapter_ColumnDefinition && $col->name == $column->name) {
return \true;
}
}
return \false;
}
/**
* Get list of columns
*
* @return string
*/
public function to_sql()
{
return \join(",", $this->_columns);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing
* @author Cody Caughlan <codycaughlan % gmail . com>
* @author Salimane Adjao Moustapha <me@salimane.com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Exception
*
* @category Ruckusing
* @package Ruckusing
* @author Cody Caughlan <codycaughlan % gmail . com>
* @author Salimane Adjao Moustapha <me@salimane.com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Exception extends \Exception
{
const MISSING_SCHEMA_INFO_TABLE = 100;
const INVALID_INDEX_NAME = 101;
const MISSING_TABLE = 102;
const INVALID_ADAPTER = 103;
const INVALID_ARGUMENT = 104;
const INVALID_TABLE_DEFINITION = 105;
const INVALID_TASK = 106;
const INVALID_LOG = 107;
const INVALID_CONFIG = 108;
const INVALID_TARGET_MIGRATION = 109;
const INVALID_MIGRATION_DIR = 110;
const INVALID_FRAMEWORK = 111;
const QUERY_ERROR = 112;
const INVALID_MIGRATION_METHOD = 113;
const MIGRATION_FAILED = 114;
const MIGRATION_NOT_SUPPORTED = 115;
const INVALID_DB_DIR = 116;
/**
* Redefine the exception so message isn't optional
*
* @param string $message
* @param int $code[optional]
* @param Exception $previous[optional]
*
* @return Ruckusing_Exception
*/
public function __construct($message, $code = 0, \Exception $previous = null)
{
// make sure everything is assigned properly
if (\version_compare(\PHP_VERSION, '5.3.0', '>=')) {
parent::__construct($message, $code, $previous);
} else {
parent::__construct($message, $code);
}
}
/**
* custom string representation of object
*
* @return string
*/
public function __toString()
{
return "\n" . \basename($this->file) . "({$this->line}) : {$this->message}\n";
}
/**
* Custom error handler
*
* @param integer $code
* @param string $message
* @param string $file
* @param integer $line
*/
public static function errorHandler($code, $message, $file, $line)
{
\file_put_contents('php://stderr', "\n" . \basename($file) . "({$line}) : {$message}\n\n");
if ($code != \E_WARNING && $code != \E_NOTICE) {
exit(1);
}
}
/**
* Custom exception handler
*
* @param Exception $exception
*/
public static function exceptionHandler($exception)
{
\file_put_contents('php://stderr', "\n" . \basename($exception->getFile()) . "({$exception->getLine()}) : {$exception->getMessage()}\n\n");
exit(1);
}
}

View File

@@ -0,0 +1,481 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_FrameworkRunner
*
* Primary work-horse class. This class bootstraps the framework by loading
* all adapters and tasks.
*
* @category Ruckusing
* @package Ruckusing
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_FrameworkRunner
{
/**
* reference to our DB connection
*
* @var array
*/
private $_db = null;
/**
* The currently active config
*
* @var array
*/
private $_active_db_config;
/**
* Available DB config (e.g. test,development, production)
*
* @var array
*/
private $_config = array();
/**
* Task manager
*
* @var Ruckusing_Task_Manager
*/
private $_task_mgr = null;
/**
* adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* current task name
*
* @var string
*/
private $_cur_task_name = "";
/**
* task options
*
* @var string
*/
private $_task_options = "";
/**
* Environment
* default is development
* but can also be one 'test', 'production', etc...
*
* @var string
*/
private $_env = "development";
/**
* set up some defaults
*
* @var array
*/
private $_opt_map = array('env' => 'development');
/**
* Flag to display help of task
* @see Ruckusing_FrameworkRunner::parse_args
*
* @var boolean
*/
private $_showhelp = \false;
/**
* Creates an instance of Ruckusing_Adapters_Base
*
* @param array $config The current config
* @param array $argv the supplied command line arguments
* @param Ruckusing_Util_Logger An optional custom logger
*
* @return Ruckusing_FrameworkRunner
*/
public function __construct($config, $argv, \YoastSEO_Vendor\Ruckusing_Util_Logger $log = null)
{
\set_error_handler(array('\YoastSEO_Vendor\Ruckusing_Exception', 'errorHandler'), \E_ALL);
\set_exception_handler(array('\YoastSEO_Vendor\Ruckusing_Exception', 'exceptionHandler'));
//parse arguments
$this->parse_args($argv);
//set config variables
$this->_config = $config;
//verify config array
$this->verify_db_config();
//initialize logger
$this->logger = $log;
$this->initialize_logger();
//include all adapters
$this->load_all_adapters(\YoastSEO_Vendor\RUCKUSING_BASE . \DIRECTORY_SEPARATOR . 'lib' . \DIRECTORY_SEPARATOR . 'Ruckusing' . \DIRECTORY_SEPARATOR . 'Adapter');
//initialize logger
$this->initialize_db();
//initialize tasks
$this->init_tasks();
}
/**
* Execute the current task
*/
public function execute()
{
$output = '';
if (empty($this->_cur_task_name)) {
if (isset($_SERVER["argv"][1]) && \stripos($_SERVER["argv"][1], '=') === \false) {
$output .= \sprintf("\n\tWrong Task format: %s\n", $_SERVER["argv"][1]);
}
$output .= $this->help();
} else {
if ($this->_task_mgr->has_task($this->_cur_task_name)) {
if ($this->_showhelp) {
$output .= $this->_task_mgr->help($this->_cur_task_name);
} else {
$output .= $this->_task_mgr->execute($this, $this->_cur_task_name, $this->_task_options);
}
} else {
$output .= \sprintf("\n\tTask not found: %s\n", $this->_cur_task_name);
$output .= $this->help();
}
}
if ($this->logger) {
$this->logger->close();
}
return $output;
}
/**
* Get the current adapter
*
* @return object
*/
public function get_adapter()
{
return $this->_adapter;
}
/**
* Initialize the task manager
*/
public function init_tasks()
{
$this->_task_mgr = new \YoastSEO_Vendor\Ruckusing_Task_Manager($this->_adapter, $this->_config);
}
/**
* Get the current migration dir
*
* @param string $key the module key name
*
* @return string
*/
public function migrations_directory($key = '')
{
$migration_dir = '';
if ($key) {
if (!isset($this->_config['migrations_dir'][$key])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("No module %s migration_dir set in config", $key), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
$migration_dir = $this->_config['migrations_dir'][$key] . \DIRECTORY_SEPARATOR;
} elseif (\is_array($this->_config['migrations_dir'])) {
$migration_dir = $this->_config['migrations_dir']['default'] . \DIRECTORY_SEPARATOR;
} else {
$migration_dir = $this->_config['migrations_dir'] . \DIRECTORY_SEPARATOR;
}
if (\array_key_exists('directory', $this->_config['db'][$this->_env])) {
return $migration_dir . $this->_config['db'][$this->_env]['directory'];
}
return $migration_dir . $this->_config['db'][$this->_env]['database'];
}
/**
* Get all migrations directory
*
* @return array
*/
public function migrations_directories()
{
$folder = $this->_config['db'][$this->_env]['database'];
if (\array_key_exists('directory', $this->_config['db'][$this->_env])) {
$folder = $this->_config['db'][$this->_env]['directory'];
}
$result = array();
if (\is_array($this->_config['migrations_dir'])) {
foreach ($this->_config['migrations_dir'] as $name => $path) {
$result[$name] = $path . \DIRECTORY_SEPARATOR . $folder;
}
} else {
$result['default'] = $this->_config['migrations_dir'] . \DIRECTORY_SEPARATOR . $folder;
}
return $result;
}
/**
* Get the current db schema dir
*
* @return string
*/
public function db_directory()
{
$path = $this->_config['db_dir'] . \DIRECTORY_SEPARATOR;
if (\array_key_exists('directory', $this->_config['db'][$this->_env])) {
return $path . $this->_config['db'][$this->_env]['directory'];
}
return $path . $this->_config['db'][$this->_env]['database'];
}
/**
* Initialize the db
*/
public function initialize_db()
{
$db = $this->_config['db'][$this->_env];
$adapter = $this->get_adapter_class($db['type']);
if (empty($adapter)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("No adapter available for DB type: %s", $db['type']), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
//construct our adapter
$this->_adapter = new $adapter($db, $this->logger);
}
/**
* Initialize the logger
*/
public function initialize_logger()
{
if (!$this->logger) {
if (\is_dir($this->_config['log_dir']) && !\is_writable($this->_config['log_dir'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("\n\nCannot write to log directory: " . $this->_config['log_dir'] . "\n\nCheck permissions.\n\n", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_LOG);
} elseif (!\is_dir($this->_config['log_dir'])) {
//try and create the log directory
\mkdir($this->_config['log_dir'], 0755, \true);
}
$log_name = \sprintf("%s.log", $this->_env);
$this->logger = \YoastSEO_Vendor\Ruckusing_Util_Logger::instance($this->_config['log_dir'] . \DIRECTORY_SEPARATOR . $log_name);
}
}
/**
* $argv is our complete command line argument set.
* PHP gives us:
* [0] = the actual file name we're executing
* [1..N] = all other arguments
*
* Our task name should be at slot [1]
* Anything else are additional parameters that we can pass
* to our task and they can deal with them as they see fit.
*
* @param array $argv the current command line arguments
*/
private function parse_args($argv)
{
$num_args = \count($argv);
$options = array();
for ($i = 0; $i < $num_args; $i++) {
$arg = $argv[$i];
if (\stripos($arg, ':') !== \false) {
$this->_cur_task_name = $arg;
} elseif ($arg == 'help') {
$this->_showhelp = \true;
continue;
} elseif (\stripos($arg, '=') !== \false) {
list($key, $value) = \explode('=', $arg);
$key = \strtolower($key);
// Allow both upper and lower case parameters
$options[$key] = $value;
if ($key == 'env') {
$this->_env = $value;
}
}
}
$this->_task_options = $options;
}
/**
* Update the local schema to handle multiple records versus the prior architecture
* of storing a single version. In addition take all existing migration files
* and register them in our new table, as they have already been executed.
*/
public function update_schema_for_timestamps()
{
//only create the table if it doesnt already exist
$this->_adapter->create_schema_version_table();
//insert all existing records into our new table
$migrator_util = new \YoastSEO_Vendor\Ruckusing_Util_Migrator($this->_adapter);
$files = $migrator_util->get_migration_files($this->migrations_directories(), 'up');
foreach ($files as $file) {
if ((int) $file['version'] >= \PHP_INT_MAX) {
//its new style like '20081010170207' so its not a candidate
continue;
}
//query old table, if it less than or equal to our max version, then its a candidate for insertion
$query_sql = \sprintf("SELECT version FROM %s WHERE version >= %d", \YoastSEO_Vendor\RUCKUSING_SCHEMA_TBL_NAME, $file['version']);
$existing_version_old_style = $this->_adapter->select_one($query_sql);
if (\count($existing_version_old_style) > 0) {
//make sure it doesnt exist in our new table, who knows how it got inserted?
$new_vers_sql = \sprintf("SELECT version FROM %s WHERE version = %d", $this->_adapter->get_schema_version_table_name(), $file['version']);
$existing_version_new_style = $this->_adapter->select_one($new_vers_sql);
if (empty($existing_version_new_style)) {
// use sprintf & %d to force it to be stripped of any leading zeros, we *know* this represents an old version style
// so we dont have to worry about PHP and integer overflow
$insert_sql = \sprintf("INSERT INTO %s (version) VALUES (%d)", $this->_adapter->get_schema_version_table_name(), $file['version']);
$this->_adapter->query($insert_sql);
}
}
}
}
/**
* Set an option
*
* @param string $key the key to set
* @param string $value the value to set
*/
private function set_opt($key, $value)
{
if (!$key) {
return;
}
$this->_opt_map[$key] = $value;
}
/**
* Verify db config
*/
private function verify_db_config()
{
if (!\array_key_exists($this->_env, $this->_config['db'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: '%s' DB is not configured", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
$env = $this->_env;
$this->_active_db_config = $this->_config['db'][$this->_env];
if (!\array_key_exists("type", $this->_active_db_config)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: 'type' is not set for '%s' DB", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (!\array_key_exists("host", $this->_active_db_config)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: 'host' is not set for '%s' DB", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (!\array_key_exists("database", $this->_active_db_config)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: 'database' is not set for '%s' DB", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (!\array_key_exists("user", $this->_active_db_config)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: 'user' is not set for '%s' DB", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (!\array_key_exists("password", $this->_active_db_config)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: 'password' is not set for '%s' DB", $this->_env), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (empty($this->_config['migrations_dir'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'migrations_dir' is not set in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (\is_array($this->_config['migrations_dir'])) {
if (!isset($this->_config['migrations_dir']['default'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'migrations_dir' 'default' key is not set in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
} elseif (empty($this->_config['migrations_dir']['default'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'migrations_dir' 'default' key is empty in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
} else {
$names = $paths = array();
foreach ($this->_config['migrations_dir'] as $name => $path) {
if (isset($names[$name])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'migrations_dir' '{$name}' key is defined multiples times in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (isset($paths[$path])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'migrations_dir' '{$paths[$path]}' and '{$name}' keys defined the same path in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
$names[$name] = $path;
$paths[$path] = $name;
}
}
}
if (isset($this->_task_options['module']) && !isset($this->_config['migrations_dir'][$this->_task_options['module']])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: module name %s is not set in 'migrations_dir' option in config.", $this->_task_options['module']), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (empty($this->_config['db_dir'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'db_dir' is not set in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
if (empty($this->_config['log_dir'])) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error: 'log_dir' is not set in config.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_CONFIG);
}
}
/**
* Get the adapter class
*
* @param string $db_type the database type
*
* @return string
*/
private function get_adapter_class($db_type)
{
$adapter_class = null;
switch ($db_type) {
case 'mysql':
$adapter_class = "\YoastSEO_Vendor\Ruckusing_Adapter_MySQL_Base";
break;
case 'pgsql':
$adapter_class = "\YoastSEO_Vendor\Ruckusing_Adapter_PgSQL_Base";
break;
case 'sqlite':
$adapter_class = "\YoastSEO_Vendor\Ruckusing_Adapter_Sqlite3_Base";
break;
}
return $adapter_class;
}
/**
* DB adapters are classes in lib/Ruckusing/Adapter
* and they follow the file name syntax of "<DB Name>/Base.php".
*
* See the function "get_adapter_class" in this class for examples.
*
* @param string $adapter_dir the adapter dir
*/
private function load_all_adapters($adapter_dir)
{
if (!\is_dir($adapter_dir)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Adapter dir: %s does not exist", $adapter_dir), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
return \false;
}
$files = \scandir($adapter_dir);
foreach ($files as $f) {
//skip over invalid files
if ($f == '.' || $f == ".." || !\is_dir($adapter_dir . \DIRECTORY_SEPARATOR . $f)) {
continue;
}
$adapter_class_path = $adapter_dir . \DIRECTORY_SEPARATOR . $f . \DIRECTORY_SEPARATOR . 'Base.php';
if (\file_exists($adapter_class_path)) {
require_once $adapter_class_path;
}
}
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
// TODO: dynamically list all available tasks
$output = <<<USAGE
\tUsage: php {$_SERVER['argv'][0]} <task> [help] [task parameters] [env=environment]
\thelp: Display this message
\tenv: The env command line parameter can be used to specify a different
\tdatabase to run against, as specific in the configuration file
\t(config/database.inc.php).
\tBy default, env is "development"
\ttask: In a nutshell, task names are pseudo-namespaced. The tasks that come
\twith the framework are namespaced to "db" (e.g. the tasks are "db:migrate",
\t"db:setup", etc).
\tAll tasks available actually :
\t- db:setup : A basic task to initialize your DB for migrations is
\tavailable. One should always run this task when first starting out.
\t- db:generate : A generic task which acts as a Generator for migrations.
\t- db:migrate : The primary purpose of the framework is to run migrations,
\tand the execution of migrations is all handled by just a regular ol' task.
\t- db:version : It is always possible to ask the framework (really the DB)
\twhat version it is currently at.
\t- db:status : With this taks you'll get an overview of the already
\texecuted migrations and which will be executed when running db:migrate
\t- db:schema : It can be beneficial to get a dump of the DB in raw SQL
\tformat which represents the current version.
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,304 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Migration
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Migration_Base
*
* @category Ruckusing
* @package Ruckusing_Migration
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Migration_Base
{
/**
* adapter
*
* @var \Ruckusing_Adapter_Base|\Ruckusing_Adapter_MySQL_Base|\Ruckusing_Adapter_PgSQL_Base|\Ruckusing_Adapter_Sqlite3_Base
*/
private $_adapter;
/**
* __construct
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return \Ruckusing_Migration_Base
*/
public function __construct($adapter)
{
$this->set_adapter($adapter);
}
/**
* __call
*
* @param string $name The method name
* @param array $args The parameters of method called
*
* @throws Ruckusing_Exception
*/
public function __call($name, $args)
{
throw new \YoastSEO_Vendor\Ruckusing_Exception('Method unknown (' . $name . ')', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_MIGRATION_METHOD);
}
/**
* Set an adapter
*
* @param Ruckusing_Adapter_Base $adapter the adapter to set
* @throws Ruckusing_Exception
* @return $this
*/
public function set_adapter($adapter)
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Adapter must be implement Ruckusing_Adapter_Base!', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
$this->_adapter = $adapter;
return $this;
}
/**
* Get the current adapter
*
* @return object
*/
public function get_adapter()
{
return $this->_adapter;
}
/**
* Create a database
*
* @param string $name the name of the database
* @param array $options
*
* @return boolean
*/
public function create_database($name, $options = null)
{
return $this->_adapter->create_database($name, $options);
}
/**
* Drop a database
*
* @param string $name the name of the database
*
* @return boolean
*/
public function drop_database($name)
{
return $this->_adapter->drop_database($name);
}
/**
* Drop a table
*
* @param string $tbl the name of the table
*
* @return boolean
*/
public function drop_table($tbl)
{
return $this->_adapter->drop_table($tbl);
}
/**
* Rename a table
*
* @param string $name the name of the table
* @param string $new_name the new name of the table
*
* @return boolean
*/
public function rename_table($name, $new_name)
{
return $this->_adapter->rename_table($name, $new_name);
}
/**
* Rename a column
*
* @param string $tbl_name the name of the table
* @param string $column_name the column name
* @param string $new_column_name the new column name
*
* @return boolean
*/
public function rename_column($tbl_name, $column_name, $new_column_name)
{
return $this->_adapter->rename_column($tbl_name, $column_name, $new_column_name);
}
/**
* Add a column
*
* @param string $table_name the name of the table
* @param string $column_name the column name
* @param string $type the column type
* @param array|string $options
*
* @return boolean
*/
public function add_column($table_name, $column_name, $type, $options = array())
{
return $this->_adapter->add_column($table_name, $column_name, $type, $options);
}
/**
* Remove a column
*
* @param string $table_name the name of the table
* @param string $column_name the column name
*
* @return boolean
*/
public function remove_column($table_name, $column_name)
{
return $this->_adapter->remove_column($table_name, $column_name);
}
/**
* Change a column
*
* @param string $table_name the name of the table
* @param string $column_name the column name
* @param string $type the column type
* @param array|string $options
*
* @return boolean
*/
public function change_column($table_name, $column_name, $type, $options = array())
{
return $this->_adapter->change_column($table_name, $column_name, $type, $options);
}
/**
* Add an index
*
* @param string $table_name the name of the table
* @param string $column_name the column name
* @param array|string $options
*
* @return boolean
*/
public function add_index($table_name, $column_name, $options = array())
{
return $this->_adapter->add_index($table_name, $column_name, $options);
}
/**
* Remove an index
*
* @param string $table_name the name of the table
* @param string $column_name the column name
* @param array|string $options
*
* @return boolean
*/
public function remove_index($table_name, $column_name, $options = array())
{
return $this->_adapter->remove_index($table_name, $column_name, $options);
}
/**
* Add timestamps
*
* @param string $table_name the name of the table
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
* @return boolean
*/
public function add_timestamps($table_name, $created_column_name = "created_at", $updated_column_name = "updated_at")
{
return $this->_adapter->add_timestamps($table_name, $created_column_name, $updated_column_name);
}
/**
* Remove timestamps
*
* @param string $table_name the name of the table
* @param string $created_column_name Created at column name
* @param string $updated_column_name Updated at column name
*
* @return boolean
*/
public function remove_timestamps($table_name, $created_column_name = "created_at", $updated_column_name = "updated_at")
{
return $this->_adapter->remove_timestamps($table_name, $created_column_name, $updated_column_name);
}
/**
* Create a table
* @param string $table_name the name of the table
* @param array|string $options
* @return bool|Ruckusing_Adapter_MySQL_TableDefinition|Ruckusing_Adapter_PgSQL_TableDefinition|Ruckusing_Adapter_Sqlite3_TableDefinition
*/
public function create_table($table_name, $options = array())
{
return $this->_adapter->create_table($table_name, $options);
}
/**
* Execute a query
*
* @param string $query the query or queries to run
*
* @return boolean
*/
public function execute($query)
{
return $this->_adapter->multi_query($query);
}
/**
* Select one query
*
* @param string $sql the query to run
*
* @return array
*/
public function select_one($sql)
{
return $this->_adapter->select_one($sql);
}
/**
* Select all query
*
* @param string $sql the query to run
*
* @return array
*/
public function select_all($sql)
{
return $this->_adapter->select_all($sql);
}
/**
* Execute a query
*
* @param string $sql the query to run
*
* @return boolean
*/
public function query($sql)
{
return $this->_adapter->query($sql);
}
/**
* Quote a string
*
* @param string $str the string to quote
*
* @return string
*/
public function quote_string($str)
{
return $this->_adapter->quote_string($str);
}
}
/**
* Implementation of Ruckusing_BaseMigration.
* Fix for backward compatibility, take care of old migrations files
* before switch to new structure
*
* @category Ruckusing
* @package Ruckusing_Migration
* @author (c) Cody Caughlan <codycaughlan % gmail . com>
*/
class Ruckusing_BaseMigration extends \YoastSEO_Vendor\Ruckusing_Migration_Base
{
}

View File

@@ -0,0 +1,109 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Task_Base
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Task_Base
{
/**
* the framework
*
* @var Ruckusing_FrameworkRunner
*/
private $_framework;
/**
* the adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter;
/**
* the migration directory
*
* @var string
*/
protected $_migrationDir;
/**
* Creates an instance of Ruckusing_Task_Base
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Ruckusing_Task_Base
*/
public function __construct($adapter)
{
$this->setAdapter($adapter);
}
/**
* Get the current framework
*
* @return object
*/
public function get_framework()
{
return $this->_framework;
}
/**
* Set the current framework
*
* @param Ruckusing_FrameworkRunner $fw the framework being set
*/
public function set_framework($fw)
{
if (!$fw instanceof \YoastSEO_Vendor\Ruckusing_FrameworkRunner) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Framework must be instance of Ruckusing_FrameworkRunner!', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_FRAMEWORK);
}
$this->_framework = $fw;
}
/**
* set adapter
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return Ruckusing_Task_Base
*/
public function setAdapter($adapter)
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Adapter must be implement Ruckusing_Adapter_Base!', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
$this->_adapter = $adapter;
return $this;
}
/**
* Get the current adapter
*
* @return object
*/
public function get_adapter()
{
return $this->_adapter;
}
/**
* set migration directories
*
* @param string $migrationDir Directory of migrations
*
* @return Ruckusing_Task_Base
*/
public function setMigrationsDirectory($migrationDir)
{
$this->_migrationDir = $migrationDir;
return $this;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Task_Interface
* Interface that all tasks must implement.
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
interface Ruckusing_Task_Interface
{
/**
* execute the task
*
* @param array $args Argument to the task
*
* @return string
*/
public function execute($args);
/**
* Return the usage of the task
*
* @return string
*/
public function help();
/**
* Set the migrations directory
*
* @param string $migrationDir The migration directory path
*
* @return void
*/
public function setMigrationsDirectory($migrationDir);
/**
* Set the current adapter
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return Ruckusing_Task_Interface
*/
public function setAdapter($adapter);
}

View File

@@ -0,0 +1,186 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
\define('YoastSEO_Vendor\\RUCKUSING_TASK_DIR', \YoastSEO_Vendor\RUCKUSING_BASE . \DIRECTORY_SEPARATOR . 'lib' . \DIRECTORY_SEPARATOR . 'Task');
/**
* Ruckusing_Task_Manager
*
* @category Ruckusing
* @package Ruckusing_Task
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Task_Manager
{
/**
* adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter;
/**
* tasks
*
* @var array
*/
private $_tasks = array();
/**
* Creates an instance of Ruckusing_Task_Manager
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
* @param Associative Array $config Extra configuration
*
* @return Ruckusing_Task_Manager
*/
public function __construct($adapter, $config = null)
{
$this->setAdapter($adapter);
$this->_config = $config;
$this->load_all_tasks(\YoastSEO_Vendor\RUCKUSING_TASK_DIR);
if (\is_array($config) && \array_key_exists('tasks_dir', $config)) {
$this->load_all_tasks($config['tasks_dir']);
}
}
/**
* set adapter
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return Ruckusing_Util_Migrator
*/
public function setAdapter($adapter)
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Adapter must be implement Ruckusing_Adapter_Base!', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
$this->_adapter = $adapter;
return $this;
}
/**
* Get the current adapter
*
* @return object $adapter The current adapter being used
*/
public function get_adapter()
{
return $this->_adapter;
}
/**
* Searches for the given task, and if found
* returns it. Otherwise null is returned.
*
* @param string $key The task name
*
* @return object | null
*/
public function get_task($key)
{
if (!$this->has_task($key)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Task '{$key}' is not registered.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
return $this->_tasks[$key];
}
/**
* Check if a task exists
*
* @param string $key The task name
*
* @return boolean
*/
public function has_task($key)
{
return \array_key_exists($key, $this->_tasks);
}
/**
* Register a new task name under the specified key.
* $obj is a class which implements the ITask interface
* and has an execute() method defined.
*
* @param string $key the task name
* @param object $obj the task object
*
* @return boolean
*/
public function register_task($key, $obj)
{
if (\array_key_exists($key, $this->_tasks)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Task key '%s' is already defined!", $key), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
return \false;
}
if (!$obj instanceof \YoastSEO_Vendor\Ruckusing_Task_Interface) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf('Task (' . $key . ') does not implement Ruckusing_Task_Interface', $key), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
return \false;
}
$this->_tasks[$key] = $obj;
return \true;
}
//---------------------
// PRIVATE METHODS
//---------------------
/**
* Load all taks
*
* @param string $task_dir the task dir path
*/
private function load_all_tasks($task_dir)
{
if (!\is_dir($task_dir)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Task dir: %s does not exist", $task_dir), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
return \false;
}
$namespaces = \scandir($task_dir);
foreach ($namespaces as $namespace) {
if ($namespace == '.' || $namespace == '..' || !\is_dir($task_dir . \DIRECTORY_SEPARATOR . $namespace)) {
continue;
}
$files = \scandir($task_dir . \DIRECTORY_SEPARATOR . $namespace);
$regex = '/^(\\w+)\\.php$/';
foreach ($files as $file) {
//skip over invalid files
if ($file == '.' || $file == ".." || !\preg_match($regex, $file, $matches)) {
continue;
}
require_once $task_dir . \DIRECTORY_SEPARATOR . $namespace . \DIRECTORY_SEPARATOR . $file;
$klass = \YoastSEO_Vendor\Ruckusing_Util_Naming::class_from_file_name($task_dir . \DIRECTORY_SEPARATOR . $namespace . \DIRECTORY_SEPARATOR . $file);
$task_name = \YoastSEO_Vendor\Ruckusing_Util_Naming::task_from_class_name($klass);
$this->register_task($task_name, new $klass($this->get_adapter()));
}
}
}
/**
* Execute a task
*
* @param object $framework The current framework
* @param string $task_name the task to execute
* @param array $options
*
* @return boolean
*/
public function execute($framework, $task_name, $options)
{
$task = $this->get_task($task_name);
$task->set_framework($framework);
return $task->execute($options);
}
/**
* Get display help of task
*
* @param string $task_name The task name
*
* @return string
*/
public function help($task_name)
{
$task = $this->get_task($task_name);
return $task->help();
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Util_Logger
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Util_Logger
{
/**
* Instance of logger
*
* @var Ruckusing_Util_Logger
*/
private static $_instance;
/**
* file
*
* @var string
*/
private $_file = '';
/**
* File descriptor
*
* @var resource
*/
private $_fp;
/**
* Creates an instance of Ruckusing_Util_Logger
*
* @param string $file the path to log to
*
* @return Ruckusing_Util_Logger
*/
public function __construct($file)
{
$this->_file = $file;
$this->_fp = \fopen($this->_file, "a+");
}
/**
* Close the file descriptor
*
* @return void
*/
public function __destruct()
{
$this->close();
}
/**
* Singleton for the instance
*
* @param string $logfile the path to log to
*
* @return object
*/
public static function instance($logfile)
{
if (self::$_instance !== \NULL) {
return $instance;
}
$instance = new \YoastSEO_Vendor\Ruckusing_Util_Logger($logfile);
return $instance;
}
/**
* Log a message
*
* @param string $msg message to log
*/
public function log($msg)
{
if ($this->_fp) {
$ts = \date('M d H:i:s', \time());
$line = \sprintf("%s [info] %s\n", $ts, $msg);
\fwrite($this->_fp, $line);
} else {
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("Error: logfile '%s' not open for writing!", $this->_file), \YoastSEO_Vendor\Ruckusing_Exception::INVALID_LOG);
}
}
/**
* Close the log file handler
*/
public function close()
{
if ($this->_fp) {
$closed = \fclose($this->_fp);
if ($closed) {
$this->_fp = null;
self::$_instance = null;
} else {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Error closing the log file', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_LOG);
}
}
}
}

View File

@@ -0,0 +1,299 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Implementation of Ruckusing_Util_Migrator
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Util_Migrator
{
/**
* adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* migrations
*
* @var array
*/
private $_migrations = array();
/**
* Creates an instance of Ruckusing_Util_Migrator
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Ruckusing_Util_Migrator
*/
public function __construct($adapter)
{
$this->setAdapter($adapter);
}
/**
* set adapter
*
* @param Ruckusing_Adapter_Base $adapter the current adapter
*
* @return Ruckusing_Util_Migrator
*/
public function setAdapter($adapter)
{
if (!$adapter instanceof \YoastSEO_Vendor\Ruckusing_Adapter_Base) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Adapter must be implement Ruckusing_Adapter_Base!', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ADAPTER);
}
$this->_adapter = $adapter;
return $this;
}
/**
* Return the max version number from the DB, or "0" in the case of no versions available.
* We must use strings because our date/timestamp when treated as an integer would cause overflow.
*
* @return string
*/
public function get_max_version()
{
// We only want one row but we cannot assume that we are using MySQL and use a LIMIT statement
// as it is not part of the SQL standard. Thus we have to select all rows and use PHP to return
// the record we need
$versions_nested = $this->_adapter->select_all(\sprintf("SELECT version FROM %s", $this->_adapter->get_schema_version_table_name()));
$versions = array();
foreach ($versions_nested as $v) {
$versions[] = $v['version'];
}
$num_versions = \count($versions);
if ($num_versions) {
\sort($versions);
//sorts lowest-to-highest (ascending)
return (string) $versions[$num_versions - 1];
} else {
return null;
}
}
/**
* This methods calculates the actual set of migrations that should be performed, taking into account
* the current version, the target version and the direction (up/down). When going up this method will
* skip migrations that have not been executed, when going down this method will only include migrations
* that have been executed.
*
* @param array $directories the migration dirs
* @param string $direction up/down
* @param string $destination the version to migrate to
* @param boolean $use_cache the current logger
*
* @return array
*/
public function get_runnable_migrations($directories, $direction, $destination = null, $use_cache = \true)
{
// cache migration lookups and early return if we've seen this requested set
if ($use_cache == \true) {
$key = $direction . '-' . $destination;
if (\array_key_exists($key, $this->_migrations)) {
return $this->_migrations[$key];
}
}
$runnable = array();
$migrations = array();
$migrations = $this->get_migration_files($directories, $direction);
$current = $this->find_version($migrations, $this->get_max_version());
$target = $this->find_version($migrations, $destination);
if (\is_null($target) && !\is_null($destination) && $destination > 0) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Could not find target version {$destination} in set of migrations.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_TARGET_MIGRATION);
}
$start = $direction == 'up' ? 0 : \array_search($current, $migrations);
$start = $start !== \false ? $start : 0;
$finish = \array_search($target, $migrations);
$finish = $finish !== \false ? $finish : \count($migrations) - 1;
$item_length = $finish - $start + 1;
$runnable = \array_slice($migrations, $start, $item_length);
//dont include first item if going down but not if going all the way to the bottom
if ($direction == 'down' && \count($runnable) > 0 && $target != null) {
\array_pop($runnable);
}
$executed = $this->get_executed_migrations();
$to_execute = array();
foreach ($runnable as $migration) {
//Skip ones that we have already executed
if ($direction == 'up' && \in_array($migration['version'], $executed)) {
continue;
}
//Skip ones that we never executed
if ($direction == 'down' && !\in_array($migration['version'], $executed)) {
continue;
}
$to_execute[] = $migration;
}
if ($use_cache == \true) {
$this->_migrations[$key] = $to_execute;
}
return $to_execute;
}
/**
* Generate a timestamp for the current time in UTC format
* Returns a string like '20090122193325'
*
* @return string
*/
public static function generate_timestamp()
{
return \gmdate('YmdHis', \time());
}
/**
* If we are going UP then log this version as executed, if going DOWN then delete
* this version from our set of executed migrations.
*
* @param object $version the version
* @param object $direction up/down
*
* @return string
*/
public function resolve_current_version($version, $direction)
{
if ($direction === 'up') {
$this->_adapter->set_current_version($version);
}
if ($direction === 'down') {
$this->_adapter->remove_version($version);
}
return $version;
}
/**
* Returns an array of strings which represent version numbers that we *have* migrated
*
* @return array
*/
public function get_executed_migrations()
{
return $this->executed_migrations();
}
/**
* Return a set of migration files, according to the given direction.
* If nested, then return a complex array with the migration parts broken up into parts
* which make analysis much easier.
*
* @param array $directories the migration dirs
* @param string $direction the direction up/down
*
* @return array
*/
public static function get_migration_files($directories, $direction)
{
$valid_files = array();
foreach ($directories as $name => $path) {
if (!\is_dir($path)) {
if (\mkdir($path, 0755, \true) === \FALSE) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("\n\tUnable to create migrations directory at %s, check permissions?", $path, \YoastSEO_Vendor\Ruckusing_Exception::INVALID_MIGRATION_DIR);
}
}
$files = \scandir($path);
$file_cnt = \count($files);
if ($file_cnt > 0) {
for ($i = 0; $i < $file_cnt; $i++) {
if (\preg_match('/^(\\d+)_(.*)\\.php$/', $files[$i], $matches)) {
if (\count($matches) == 3) {
$valid_files[] = array('name' => $files[$i], 'module' => $name);
}
}
}
}
}
\usort($valid_files, array("\YoastSEO_Vendor\Ruckusing_Util_Migrator", "migration_compare"));
//sorts in place
if ($direction == 'down') {
$valid_files = \array_reverse($valid_files);
}
//user wants a nested structure
$files = array();
$cnt = \count($valid_files);
for ($i = 0; $i < $cnt; $i++) {
$migration = $valid_files[$i];
if (\preg_match('/^(\\d+)_(.*)\\.php$/', $migration['name'], $matches)) {
$files[] = array('version' => $matches[1], 'class' => $matches[2], 'file' => $matches[0], 'module' => $migration['module']);
}
}
return $files;
}
/**
* Find the specified structure (representing a migration) that matches the given version
*
* @param array $migrations the list of migrations
* @param string $version the version being searched
* @param boolean $only_index whether to only return the index of the version
*
* @return null | integer | array
*/
public function find_version($migrations, $version, $only_index = \false)
{
$len = \count($migrations);
for ($i = 0; $i < $len; $i++) {
if ($migrations[$i]['version'] == $version) {
return $only_index ? $i : $migrations[$i];
}
}
return null;
}
//== Private methods
/**
* Custom comparator for migration sorting
*
* @param array $a first migration structure
* @param array $b second migration structure
*
* @return integer
*/
private static function migration_compare($a, $b)
{
return \strcmp($a["name"], $b["name"]);
}
/**
* Find the index of the migration in the set of migrations that match the given version
*
* @param array $migrations the list of migrations
* @param string $version the version being searched
*
* @return integer
*/
private function find_version_index($migrations, $version)
{
//edge case
if (\is_null($version)) {
return null;
}
$len = \count($migrations);
for ($i = 0; $i < $len; $i++) {
if ($migrations[$i]['version'] == $version) {
return $i;
}
}
return null;
}
/**
* Query the database and return a list of migration versions that *have* been executed
*
* @return array
*/
private function executed_migrations()
{
$query_sql = \sprintf('SELECT version FROM %s', $this->_adapter->get_schema_version_table_name());
$versions = $this->_adapter->select_all($query_sql);
$executed = array();
foreach ($versions as $v) {
$executed[] = $v['version'];
}
\sort($executed);
return $executed;
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Ruckusing_Util_Naming
* This utility class maps class names between their task names, back and forth.
*
* This framework relies on conventions which allow us to make certain
* assumptions.
*
* Example valid task names are "db:version" which maps to a PHP class called DB_Version.
*
* Namely, underscores are converted to colons, the first part of the task name is upper-cased
* and the first character of the second part is capitalized.
*
* Using this convention one can easily go back and forth between task names and PHP Class names.
*
* @category Ruckusing
* @package Ruckusing_Util
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Ruckusing_Util_Naming
{
/**
* prefix of class name
*
* @var string
*/
const CLASS_NS_PREFIX = '\YoastSEO_Vendor\Task_';
/**
* Get the corresponding task from a class name
*
* @param string $klass the class name
*
* @return string
*/
public static function task_from_class_name($klass)
{
if (!\preg_match('/' . preg_quote(self::CLASS_NS_PREFIX) . '/', $klass)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('The class name must start with ' . self::CLASS_NS_PREFIX, \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
//strip namespace
$klass = \str_replace(self::CLASS_NS_PREFIX, '', $klass);
$klass = \strtolower($klass);
$klass = \str_replace("_", ":", $klass);
return $klass;
}
/**
* Convert a task to its corresponding class name
*
* @param string $task the task name
*
* @return string
*/
public static function task_to_class_name($task)
{
if (\false === \stripos($task, ':')) {
throw new \YoastSEO_Vendor\Ruckusing_Exception('Task name (' . $task . ') must be contains ":"', \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$parts = \explode(":", $task);
return self::CLASS_NS_PREFIX . \ucfirst($parts[0]) . '_' . \ucfirst($parts[1]);
}
/**
* Find class from filename
*
* @param string $file_name the migration filename
*
* @return string
*/
public static function class_from_file_name($file_name)
{
//we could be given either a string or an absolute path
//deal with it appropriately
// normalize directory separators first
$file_name = \str_replace(array('/', '\\'), \DIRECTORY_SEPARATOR, $file_name);
$parts = \explode(\DIRECTORY_SEPARATOR, $file_name);
$namespace = $parts[\count($parts) - 2];
$file_name = \substr($parts[\count($parts) - 1], 0, -4);
return self::CLASS_NS_PREFIX . \ucfirst($namespace) . '_' . \ucfirst($file_name);
}
/**
* Find class from migration file
*
* @param string $file_name the migration filename
*
* @return string
*/
public static function class_from_migration_file($file_name)
{
$className = \false;
if (\preg_match('/^(\\d+)_(.*)\\.php$/', $file_name, $matches)) {
if (\count($matches) == 3) {
$className = $matches[2];
}
}
return $className;
}
/**
* Transform to camelcase
*
* @param string $str the task name
*
* @return string
*/
public static function camelcase($str)
{
$str = \preg_replace('/\\s+/', '_', $str);
$parts = \explode("_", $str);
//if there were no spaces in the input string
//then assume its already camel-cased
if (\count($parts) == 0) {
return $str;
}
$cleaned = "";
foreach ($parts as $word) {
$cleaned .= \ucfirst($word);
}
return $cleaned;
}
/**
* Get an index name
*
* @param string $table_name the table name
* @param string $column_name the column name
*
* @return string
*/
public static function index_name($table_name, $column_name)
{
$name = \sprintf("idx_%s", self::underscore($table_name));
//if the column parameter is an array then the user wants to create a multi-column
//index
if (\is_array($column_name)) {
$column_str = \join("_and_", $column_name);
} else {
$column_str = $column_name;
}
$name .= \sprintf("_%s", $column_str);
return $name;
}
/**
* Finds underscore
*
* @param string $str the task name
*
* @return boolean
*/
public static function underscore($str)
{
$underscored = \preg_replace('/\\W/', '_', $str);
return \preg_replace('/\\_{2,}/', '_', $underscored);
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Task_DB_Generate
* generic task which acts as a Generator for migrations.
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @author Salimane Adjao Moustapha <me@salimane.com>
*/
class Task_Db_Generate extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* Creates an instance of Task_DB_Generate
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Generate
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
$output = '';
// Add support for old migration style
if (!\is_array($args) || !\array_key_exists('name', $args)) {
$cargs = $this->parse_args($_SERVER['argv']);
//input sanity check
if (!\is_array($cargs) || !\array_key_exists('name', $cargs)) {
$output .= $this->help();
return $output;
}
$migration_name = $cargs['name'];
} else {
$migration_name = $args['name'];
}
if (!\array_key_exists('module', $args)) {
$args['module'] = '';
}
//clear any filesystem stats cache
\clearstatcache();
$framework = $this->get_framework();
$migrations_dir = $framework->migrations_directory($args['module']);
if (!\is_dir($migrations_dir)) {
$output .= "\n\tMigrations directory (" . $migrations_dir . " doesn't exist, attempting to create.\n";
if (\mkdir($migrations_dir, 0755, \true) === \FALSE) {
$output .= "\n\tUnable to create migrations directory at " . $migrations_dir . ", check permissions?\n";
} else {
$output .= "\n\tCreated OK\n";
}
}
//generate a complete migration file
$next_version = \YoastSEO_Vendor\Ruckusing_Util_Migrator::generate_timestamp();
$class = \YoastSEO_Vendor\Ruckusing_Util_Naming::camelcase($migration_name);
if (!self::classNameIsCorrect($class)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Bad migration name,PHP class can't be named as {$class}.Please, choose another name.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$all_dirs = $framework->migrations_directories();
if ($re = self::classNameIsDuplicated($class, $all_dirs)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("This migration name is already used in the \"{$re}\" directory. Please, choose another name.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_ARGUMENT);
}
$file_name = $next_version . '_' . $class . '.php';
//check to make sure our destination directory is writable
if (!\is_writable($migrations_dir)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("ERROR: migration directory '" . $migrations_dir . "' is not writable by the current user. Check permissions and try again.", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_MIGRATION_DIR);
}
//write it out!
$full_path = $migrations_dir . \DIRECTORY_SEPARATOR . $file_name;
$template_str = self::get_template($class);
$file_result = \file_put_contents($full_path, $template_str);
if ($file_result === \FALSE) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("Error writing to migrations directory/file. Do you have sufficient privileges?", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_MIGRATION_DIR);
} else {
$output .= "\n\tCreated migration: {$file_name}\n\n";
}
return $output;
}
/**
* Parse command line arguments.
*
* @param array $argv The current supplied command line arguments.
*
* @return array ('name' => 'name')
*/
public function parse_args($argv)
{
foreach ($argv as $i => $arg) {
if (\strpos($arg, '=') !== \FALSE) {
unset($argv[$i]);
}
}
$num_args = \count($argv);
if ($num_args < 3) {
return array();
}
$migration_name = $argv[2];
return array('name' => $migration_name);
}
/**
* Indicate if a class name is already used
*
* @param string $classname The class name to test
* @param string $migrationsDirs The array with directories of migration files (in simplest case - just array with one element)
*
* @return bool
*/
public static function classNameIsDuplicated($classname, $migrationsDirs)
{
$migrationFiles = \YoastSEO_Vendor\Ruckusing_Util_Migrator::get_migration_files($migrationsDirs, 'up');
$classname = \strtolower($classname);
foreach ($migrationFiles as $file) {
if (\strtolower($file['class']) == $classname) {
return $file['module'];
}
}
return \false;
}
/**
* Indicate if a class name is correct or not.
*
* @param string $classname The class name to test
*
* @return bool
*/
public static function classNameIsCorrect($classname)
{
$correct_class_name_regex = '/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/';
if (\preg_match($correct_class_name_regex, $classname)) {
return \true;
}
return \false;
}
/**
* generate a migration template string
*
* @param string $klass class name to create
* @return string
*/
public static function get_template($klass)
{
$template = <<<TPL
<?php
class {$klass} extends Ruckusing_Migration_Base
{
public function up()
{
}//up()
public function down()
{
}//down()
}
TPL;
return $template;
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:generate <migration name>
\tGenerator for migrations.
\t<migration name> is a descriptive name of the migration,
\tjoined with underscores. e.g.: add_index_to_users | create_users_table
\tExample :
\t\tphp {$_SERVER['argv'][0]} db:generate add_index_to_users
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,347 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
\define('YoastSEO_Vendor\\STYLE_REGULAR', 1);
\define('YoastSEO_Vendor\\STYLE_OFFSET', 2);
/**
* Task_DB_Migrate.
* This is the primary work-horse method, it runs all migrations available,
* up to the current version.
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Task_Db_Migrate extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* migrator util
*
* @var Ruckusing_Util_Migrator
*/
private $_migrator_util = null;
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* migrator directories
*
* @var string
*/
private $_migratorDirs = null;
/**
* The task arguments
*
* @var array
*/
private $_task_args = array();
/**
* debug
*
* @var boolean
*/
private $_debug = \false;
/**
* Return executed string
*
* @var string
*/
private $_return = '';
/**
* Creates an instance of Task_DB_Migrate
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Migrate
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
$this->_migrator_util = new \YoastSEO_Vendor\Ruckusing_Util_Migrator($this->_adapter);
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
if (!$this->_adapter->supports_migrations()) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("This database does not support migrations.", \YoastSEO_Vendor\Ruckusing_Exception::MIGRATION_NOT_SUPPORTED);
}
$this->_task_args = $args;
$this->_return .= "Started: " . \date('Y-m-d g:ia T') . "\n\n";
$this->_return .= "[db:migrate]: \n";
try {
// Check that the schema_version table exists, and if not, automatically create it
$this->verify_environment();
$target_version = null;
$style = \YoastSEO_Vendor\STYLE_REGULAR;
//did the user specify an explicit version?
if (\array_key_exists('version', $this->_task_args)) {
$target_version = \trim($this->_task_args['version']);
}
// did the user specify a relative offset, e.g. "-2" or "+3" ?
if ($target_version !== null) {
if (\preg_match('/^([\\-\\+])(\\d+)$/', $target_version, $matches)) {
if (\count($matches) == 3) {
$direction = $matches[1] == '-' ? 'down' : 'up';
$steps = \intval($matches[2]);
$style = \YoastSEO_Vendor\STYLE_OFFSET;
}
}
}
//determine our direction and target version
$current_version = $this->_migrator_util->get_max_version();
if ($style == \YoastSEO_Vendor\STYLE_REGULAR) {
if (\is_null($target_version)) {
$this->prepare_to_migrate($target_version, 'up');
} elseif ($current_version > $target_version) {
$this->prepare_to_migrate($target_version, 'down');
} else {
$this->prepare_to_migrate($target_version, 'up');
}
}
if ($style == \YoastSEO_Vendor\STYLE_OFFSET) {
$this->migrate_from_offset($steps, $current_version, $direction);
}
// Completed - display accumulated output
if (!empty($output)) {
$this->_return .= "\n\n";
}
} catch (\YoastSEO_Vendor\Ruckusing_Exception $ex) {
if ($ex->getCode() == \YoastSEO_Vendor\Ruckusing_Exception::MISSING_SCHEMA_INFO_TABLE) {
$this->_return .= "\tSchema info table does not exist. I tried creating it but failed. Check permissions.";
} else {
throw $ex;
}
}
$this->_return .= "\n\nFinished: " . \date('Y-m-d g:ia T') . "\n\n";
return $this->_return;
}
/**
* Migrate to a specific version using steps from current version
*
* @param integer $steps number of versions to jump to
* @param string $current_version current version
* @param $string $direction direction to migrate to 'up'/'down'
*/
private function migrate_from_offset($steps, $current_version, $direction)
{
$migrations = $this->_migrator_util->get_migration_files($this->_migratorDirs, $direction);
$current_index = $this->_migrator_util->find_version($migrations, $current_version, \true);
$current_index = $current_index !== null ? $current_index : -1;
if ($this->_debug == \true) {
$this->_return .= \print_r($migrations, \true);
$this->_return .= "\ncurrent_index: " . $current_index . "\n";
$this->_return .= "\ncurrent_version: " . $current_version . "\n";
$this->_return .= "\nsteps: " . $steps . " {$direction}\n";
}
// If we are not at the bottom then adjust our index (to satisfy array_slice)
if ($current_index == -1 && $direction === 'down') {
$available = array();
} else {
if ($direction === 'up') {
$current_index += 1;
} else {
$current_index += $steps;
}
// check to see if we have enough migrations to run - the user
// might have asked to run more than we have available
$available = \array_slice($migrations, $current_index, $steps);
}
$target = \end($available);
if ($this->_debug == \true) {
$this->_return .= "\n------------- TARGET ------------------\n";
$this->_return .= \print_r($target, \true);
}
$this->prepare_to_migrate(isset($target['version']) ? $target['version'] : null, $direction);
}
/**
* Prepare to do a migration
*
* @param string $destination version to migrate to
* @param $string $direction direction to migrate to 'up'/'down'
*/
private function prepare_to_migrate($destination, $direction)
{
try {
$this->_return .= "\tMigrating " . \strtoupper($direction);
if (!\is_null($destination)) {
$this->_return .= " to: {$destination}\n";
} else {
$this->_return .= ":\n";
}
$migrations = $this->_migrator_util->get_runnable_migrations($this->_migratorDirs, $direction, $destination);
if (\count($migrations) == 0) {
$this->_return .= "\nNo relevant migrations to run. Exiting...\n";
return;
}
$result = $this->run_migrations($migrations, $direction, $destination);
} catch (\Exception $ex) {
throw $ex;
}
}
/**
* Run migrations
*
* @param array $migrations nigrations to run
* @param $string $target_method direction to migrate to 'up'/'down'
* @param string $destination version to migrate to
*
* @return array
*/
private function run_migrations($migrations, $target_method, $destination)
{
$last_version = -1;
foreach ($migrations as $file) {
$full_path = $this->_migratorDirs[$file['module']] . \DIRECTORY_SEPARATOR . $file['file'];
if (\is_file($full_path) && \is_readable($full_path)) {
require_once $full_path;
$klass = \YoastSEO_Vendor\Ruckusing_Util_Naming::class_from_migration_file($file['file']);
$obj = new $klass($this->_adapter);
$start = $this->start_timer();
try {
//start transaction
$this->_adapter->start_transaction();
$result = $obj->{$target_method}();
//successfully ran migration, update our version and commit
$this->_migrator_util->resolve_current_version($file['version'], $target_method);
$this->_adapter->commit_transaction();
} catch (\YoastSEO_Vendor\Ruckusing_Exception $e) {
$this->_adapter->rollback_transaction();
//wrap the caught exception in our own
throw new \YoastSEO_Vendor\Ruckusing_Exception(\sprintf("%s - %s", $file['class'], $e->getMessage()), \YoastSEO_Vendor\Ruckusing_Exception::MIGRATION_FAILED);
}
$end = $this->end_timer();
$diff = $this->diff_timer($start, $end);
$this->_return .= \sprintf("========= %s ======== (%.2f)\n", $file['class'], $diff);
$last_version = $file['version'];
$exec = \true;
}
}
//update the schema info
$result = array('last_version' => $last_version);
return $result;
}
/**
* Start Timer
*
* @return int
*/
private function start_timer()
{
return \microtime(\true);
}
/**
* End Timer
*
* @return int
*/
private function end_timer()
{
return \microtime(\true);
}
/**
* Calculate the time difference
*
* @param int $s the start time
* @param int $e the end time
*
* @return int
*/
private function diff_timer($s, $e)
{
return $e - $s;
}
/**
* Check the environment and create the migration dir if it doesn't exists
*/
private function verify_environment()
{
if (!$this->_adapter->table_exists($this->_adapter->get_schema_version_table_name())) {
$this->_return .= "\n\tSchema version table does not exist. Auto-creating.";
$this->auto_create_schema_info_table();
}
$this->_migratorDirs = $this->get_framework()->migrations_directories();
// create the migrations directory if it doesnt exist
foreach ($this->_migratorDirs as $name => $path) {
if (!\is_dir($path)) {
$this->_return .= \sprintf("\n\tMigrations directory (%s) doesn't exist, attempting to create.", $path);
if (\mkdir($path, 0755, \true) === \FALSE) {
$this->_return .= \sprintf("\n\tUnable to create migrations directory at %s, check permissions?", $path);
} else {
$this->_return .= \sprintf("\n\tCreated OK");
}
}
}
}
/**
* Create the schema
*
* @return boolean
*/
private function auto_create_schema_info_table()
{
try {
$this->_return .= \sprintf("\n\tCreating schema version table: %s", $this->_adapter->get_schema_version_table_name() . "\n\n");
$this->_adapter->create_schema_version_table();
return \true;
} catch (\Exception $e) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("\nError auto-creating 'schema_info' table: " . $e->getMessage() . "\n\n", \YoastSEO_Vendor\Ruckusing_Exception::MIGRATION_FAILED);
}
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:migrate [VERSION]
\tThe primary purpose of the framework is to run migrations, and the
\texecution of migrations is all handled by just a regular ol' task.
\tVERSION can be specified to go up (or down) to a specific
\tversion, based on the current version. If not specified,
\tall migrations greater than the current database version
\twill be executed.
\tExample A: The database is fresh and empty, assuming there
\tare 5 actual migrations, but only the first two should be run.
\t\tphp {$_SERVER['argv'][0]} db:migrate VERSION=20101006114707
\tExample B: The current version of the DB is 20101006114707
\tand we want to go down to 20100921114643
\t\tphp {$_SERVER['argv'][0]} db:migrate VERSION=20100921114643
\tExample C: You can also use relative number of revisions
\t(positive migrate up, negative migrate down).
\t\tphp {$_SERVER['argv'][0]} db:migrate VERSION=-2
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Task_DB_Schema
* generic task which dumps the schema of the DB
* as a text file.
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Task_Db_Schema extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* Return executed string
*
* @var string
*/
private $_return = '';
/**
* Creates an instance of Task_DB_Schema
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Schema
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
$this->_return .= "Started: " . \date('Y-m-d g:ia T') . "\n\n";
$this->_return .= "[db:schema]: \n";
//write to disk
$schema_file = $this->db_dir() . '/schema.txt';
$schema = $this->_adapter->schema($schema_file);
$this->_return .= "\tSchema written to: {$schema_file}\n\n";
$this->_return .= "\n\nFinished: " . \date('Y-m-d g:ia T') . "\n\n";
return $this->_return;
}
/**
* Get the db dir, check and create the db dir if it doesn't exists
*
* @return string
*/
private function db_dir()
{
// create the db directory if it doesnt exist
$db_directory = $this->get_framework()->db_directory();
if (!\is_dir($db_directory)) {
$this->_return .= \sprintf("\n\tDB Schema directory (%s doesn't exist, attempting to create.\n", $db_directory);
if (\mkdir($db_directory, 0755, \true) === \FALSE) {
$this->_return .= \sprintf("\n\tUnable to create migrations directory at %s, check permissions?\n", $db_directory);
} else {
$this->_return .= \sprintf("\n\tCreated OK\n\n");
}
}
//check to make sure our destination directory is writable
if (!\is_writable($db_directory)) {
throw new \YoastSEO_Vendor\Ruckusing_Exception("ERROR: DB Schema directory '" . $db_directory . "' is not writable by the current user. Check permissions and try again.\n", \YoastSEO_Vendor\Ruckusing_Exception::INVALID_DB_DIR);
}
return $db_directory;
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:schema
\tIt can be beneficial to get a dump of the DB in raw SQL format which represents
\tthe current version.
\tNote: This dump only contains the actual schema (e.g. the DML needed to
\treconstruct the DB), but not any actual data.
\tIn MySQL terms, this task would not be the same as running the mysqldump command
\t(which by defaults does include any data in the tables).
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Task_DB_Setup.
* This is a generic task which initializes a table to hold migration version information.
* This task is non-destructive and will only create the table if it does not already exist, otherwise
* no other actions are performed.
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Task_Db_Setup extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* Creates an instance of Task_DB_Setup
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Setup
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
$output = "Started: " . \date('Y-m-d g:ia T') . "\n\n";
$output .= "[db:setup]: \n";
//it doesnt exist, create it
if (!$this->_adapter->table_exists($this->_adapter->get_schema_version_table_name())) {
$output .= \sprintf("\tCreating table: %s", $this->_adapter->get_schema_version_table_name());
$this->_adapter->create_schema_version_table();
$output .= "\n\tDone.\n";
} else {
$output .= \sprintf("\tNOTICE: table '%s' already exists. Nothing to do.", $this->_adapter->get_schema_version_table_name());
}
$output .= "\n\nFinished: " . \date('Y-m-d g:ia T') . "\n\n";
return $output;
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:setup
\tA basic task to initialize your DB for migrations is available. One should
\talways run this task when first starting out.
\tThis task does not take arguments.
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Task_DB_Status.
* Prints out a list of migrations that have and haven't been applied
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Task_Db_Status extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* Creates an instance of Task_DB_Status
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Status
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
$output = "Started: " . \date('Y-m-d g:ia T') . "\n\n";
$output .= "[db:status]: \n";
$util = new \YoastSEO_Vendor\Ruckusing_Util_Migrator($this->_adapter);
$migrations = $util->get_executed_migrations();
$files = $util->get_migration_files($this->get_framework()->migrations_directories(), 'up');
$applied = array();
$not_applied = array();
foreach ($files as $file) {
if (\in_array($file['version'], $migrations)) {
$applied[] = $file['class'] . ' [ ' . $file['version'] . ' ]';
} else {
$not_applied[] = $file['class'] . ' [ ' . $file['version'] . ' ]';
}
}
if (\count($applied) > 0) {
$output .= $this->_displayMigrations($applied, 'APPLIED');
}
if (\count($not_applied) > 0) {
$output .= $this->_displayMigrations($not_applied, 'NOT APPLIED');
}
$output .= "\n\nFinished: " . \date('Y-m-d g:ia T') . "\n\n";
return $output;
}
/**
* display migrations results
*
* @param array $migrations The migrations
* @param string $title The title of section
*
* @return string
*/
private function _displayMigrations($migrations, $title)
{
$output = "\n\n===================== {$title} =======================\n";
foreach ($migrations as $a) {
$output .= "\t" . $a . "\n";
}
return $output;
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:status
\tWith this task you'll get an overview of the already executed migrations and
\twhich will be executed when running db:migrate.
\tThis task does not take arguments.
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace YoastSEO_Vendor;
/**
* Ruckusing
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
/**
* Task_DB_Version.
* This task retrieves the current version of the schema.
*
* @category Ruckusing
* @package Task
* @subpackage Db
* @author Cody Caughlan <codycaughlan % gmail . com>
* @link https://github.com/ruckus/ruckusing-migrations
*/
class Task_Db_Version extends \YoastSEO_Vendor\Ruckusing_Task_Base implements \YoastSEO_Vendor\Ruckusing_Task_Interface
{
/**
* Current Adapter
*
* @var Ruckusing_Adapter_Base
*/
private $_adapter = null;
/**
* Creates an instance of Task_DB_Version
*
* @param Ruckusing_Adapter_Base $adapter The current adapter being used
*
* @return Task_DB_Version
*/
public function __construct($adapter)
{
parent::__construct($adapter);
$this->_adapter = $adapter;
}
/**
* Primary task entry point
*
* @param array $args The current supplied options.
*/
public function execute($args)
{
$output = "Started: " . \date('Y-m-d g:ia T') . "\n\n";
$output .= "[db:version]: \n";
if (!$this->_adapter->table_exists($this->_adapter->get_schema_version_table_name())) {
//it doesnt exist, create it
$output .= "\tSchema version table (" . $this->_adapter->get_schema_version_table_name() . ") does not exist. Do you need to run 'db:setup'?";
} else {
//it exists, read the version from it
// We only want one row but we cannot assume that we are using MySQL and use a LIMIT statement
// as it is not part of the SQL standard. Thus we have to select all rows and use PHP to return
// the record we need
$versions_nested = $this->_adapter->select_all(\sprintf("SELECT version FROM %s", $this->_adapter->get_schema_version_table_name()));
$versions = array();
foreach ($versions_nested as $v) {
$versions[] = $v['version'];
}
$num_versions = \count($versions);
if ($num_versions > 0) {
\sort($versions);
//sorts lowest-to-highest (ascending)
$version = (string) $versions[$num_versions - 1];
$output .= \sprintf("\tCurrent version: %s", $version);
} else {
$output .= \sprintf("\tNo migrations have been executed.");
}
}
$output .= "\n\nFinished: " . \date('Y-m-d g:ia T') . "\n\n";
return $output;
}
/**
* Return the usage of the task
*
* @return string
*/
public function help()
{
$output = <<<USAGE
\tTask: db:version
\tIt is always possible to ask the framework (really the DB) what version it is
\tcurrently at.
\tThis task does not take arguments.
USAGE;
return $output;
}
}

View File

@@ -0,0 +1,5 @@
<?php
namespace YoastSEO_Vendor;
\define('YoastSEO_Vendor\\RUCKUSING_VERSION', '1.1.0');