<?php
/**
 * pixabay-php-api
 * PixabayClient API
 *
 * PHP Version 5
 *
 * @category Production
 * @package  Default
 * @author   Philipp Tkachev <zoonman@gmail.com>
 * @date     12/14/14 9:18 AM
 * @license  https://www.zoonman.com/projects/pixabay/license.txt MIT
 * @version  GIT: 1.0
 * @link     https://www.zoonman.com/projects/pixabay/
 */

namespace Pixabay;

use GuzzleHttp\Client;

/**
 * Class PixabayClient
 * @package Pixabay\PixabayClient
 */
class PixabayClient {
    /**
     * @var array
     */
    private $options = [];

    /**
     * @var array
     */
    private $optionsList = [
        'key',
        'response_group',
        'id',
        'q',
        'lang',
        'callback',
        'image_type',
        'orientation',
        'category',
        'min_width',
        'min_height',
        'editors_choice',
        'safesearch',
        'page',
        'per_page',
        'pretty',
        'response_group',
        'order',
        'video_type'
    ];

    /**
     * @var Client
     */
    private $client;

    /**
     * Root of Pixabay REST API
     */
    const API_ROOT = 'https://pixabay.com/api/';
    const SEGMENT_IMAGES = '';
    const SEGMENT_VIDEOS = 'videos/';

    /**
     * Class constructor
     *
     * @param array $options
     * @throws \Exception
     */
    public function __construct(array $options)
    {
        $this->client = new Client(['base_url' => self::API_ROOT]);
        if (empty($options['key'])) {
            throw new \Exception('You must specify "key" parameter in constructor options');
        }
        $this->parseOptions($options);
    }

    /**
     * Parse Provided options
     *
     * @param array $options
     * @param bool $resetOptions
     */
    private function parseOptions(array $options, $resetOptions = false)
    {
        foreach ($this->optionsList as $option) {
            if (isset($options[$option])) {
                $this->options[$option] = $options[$option];
            } elseif ($resetOptions && isset($this->options[$option])) {
                unset($this->options[$option]);
            }
        }
    }

    /**
     * Get Data from Pixabay API
     *
     * @param array  $options
     * @param bool   $returnObject
     * @param bool   $resetOptions
     * @param string $segment
     * @return mixed
     */
    public function get(array $options = [], $returnObject = false, $resetOptions = false, $segment = self::SEGMENT_IMAGES)
    {
        $this->parseOptions($options, $resetOptions);
        $response = $this->client->request('GET', self::API_ROOT . $segment, ['query' => $this->options]);
        $data = $response->getBody()->getContents();
        return json_decode($data, $returnObject);
    }

    /**
     * Get Images from Pixabay API
     *
     * @param array $options
     * @param bool  $returnObject
     * @param bool  $resetOptions
     *
     * @return mixed
     */
    public function getImages(array $options = [], $returnObject = false, $resetOptions = false)
    {
        return $this->get($options, $returnObject, $resetOptions, self::SEGMENT_IMAGES);
    }

    /**
     * Get Videos from Pixabay API
     *
     * @param array $options
     * @param bool  $returnObject
     * @param bool  $resetOptions
     *
     * @return mixed
     */
    public function getVideos(array $options = [], $returnObject = false, $resetOptions = false)
    {
        return $this->get($options, $returnObject, $resetOptions, self::SEGMENT_VIDEOS);
    }
}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump\abstracts;

use yii\base\Component;
use yii\helpers\Console;

/**
 * Class AbstractOutput
 * @package hzhihua\dump\abstracts
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
abstract class AbstractOutput extends Component
{

    /**
     * @param $content array the parameters (name-value pairs) that will be extracted and made available in the view file.
     * @see \yii\base\View::renderPhpFile()
     * @param $template string template file path
     * @param $outputFile string generate file path
     * @return bool
     */
    abstract public function generateFile(array $content, $template, $outputFile);

    /**
     * printf some thing at the terminal and record start time
     * @param string $string
     * @return int exit code
     */
    abstract public function startPrintf($string);

    /**
     * printf some thing at the terminal and record end time
     * @param string $string
     * @return int exit code
     */
    abstract public function endPrintf($string);

    /**
     * print conclusion at the terminal
     * @param $handleTable array table name that had been generate
     * @param $filterTable array table name that had been filter
     */
    abstract public function conclusion($handleTable, $filterTable);

    /**
     * Prints a string to STDOUT
     *
     * You may optionally format the string with ANSI codes by
     * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
     *
     * Example:
     *
     * ```
     * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
     * ```
     *
     * @param string $string the string to print
     * @return int|bool Number of bytes printed or false on error
     */
    public static function stdout($string)
    {
        $args = func_get_args();
        array_shift($args);

        if (empty($args)) {
            return Console::stdout($string);
        }

        $string = Console::ansiFormat($string, $args);
        return Console::stdout($string);
    }
}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump\models;

use yii\db\Connection;
use yii\db\Query;
use yii\db\TableSchema;
use yii\db\Expression;
use yii\db\ColumnSchema;
use hzhihua\dump\abstracts\AbstractSchema;

/**
 * Class Schema
 * Handle table
 *
 * @package hzhihua\dump\models
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
class Schema extends AbstractSchema
{
    /**
     * enter 换行符号
     */
    const ENTER = "\n";

    /**
     * text ident format
     * 4 space
     */
    public $textIndent = '    ';

    /**
     * cache sql result "SHOW TABLE STATUS"
     * @var null
     */
    protected $_tableStatus = null;

    /**
     * table list
     * @param Connection $db
     * @param string $sparactor "table1, table2, table3"
     * @return string
     */
    public function getTableList(Connection $db, $sparactor = ', ')
    {
        $tableList = '';

        foreach ($db->schema->getTableNames() as $tableName) {
            $tableList .= static::removePrefix($tableName, $this->db->tablePrefix) . $sparactor;
        }

        return rtrim($tableList, $sparactor);
    }

    /**
     * get table safeUp definition with primary key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getTable(TableSchema $table, $indent = 0)
    {
        $definition =
            $this->getCreateTable($table->name, $indent)
            . $this->getColumns($table->columns, $indent + 1)
            . $this->getTableOptions($table, $indent)
        ;

        return $definition;
    }

    /**
     * get table safeDown definition
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getDropTable(TableSchema $table, $indent = 0)
    {
        // Do not run this sql: "drop table `tableName`", it will drop the table that has exists before running "./yii migrate"

        $textIndent = $this->textIndent($indent);
        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        $definition = <<<DEFINITION
{$textIndent}foreach (\$this->runSuccess as \$keyName => \$value) {
{$textIndent}    if ('createTable' === \$keyName) {
{$textIndent}        \$this->dropTable('{{%$tableName}}');
{$textIndent}    } elseif ('addTableComment' === \$keyName) {
{$textIndent}        \$this->dropCommentFromTable('{{%$tableName}}');
{$textIndent}    } else {
{$textIndent}        throw new \yii\db\Exception('only support "dropTable" and "dropCommentFromTable"');
{$textIndent}    }
{$textIndent}}
DEFINITION;

        return $definition;

    }

    /**
     * fetch table data limit(start,end)
     * @param TableSchema $table
     * @param $limit string select data from table limit ...
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getTableData(TableSchema $table, $limit = null, $indent = 0)
    {

        if (null === $limit) {
            return '';
        }

        $data = [];
        $_limit = '';
        $batchInsertLength = 500;
        $textIndent = $this->textIndent($indent);
        $definition = $textIndent . '$this->_transaction = $this->getDb()->beginTransaction();' . self::ENTER;

        if (is_bool($limit)) { // -limit
            $_limit = [null, null]; // select all data
        } elseif (empty($limit)) { // -limit=
            $_limit = [null, null]; // select all data
        } elseif (false !== strpos($limit, ',')) { // -limit=1,10 or -limit=1,
            $_limit = explode(',', $limit);

            if (empty($_limit[1])) { // -limit=1,
                $_limit[1] = null;
            }
        } else { // -limit=1
            $_limit[0] = 0;
            $_limit[1] = $limit;
        }

        $limitCount = $_limit[1] - $_limit[0];

        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        $tableColumns = $table->columns;

        // 获取字段名称
        $columns = $this->textIndent($indent + 1) . '[';
        foreach ($tableColumns as $value) {
            $columns .= "'{$value->name}', ";
        }
        $columns = rtrim($columns, ', ') . ']';

        $query = (new Query())
            ->select('*')
            ->from($table->name)
            ->offset($_limit[0])
            ->limit($_limit[1])
        ;

        if ($limitCount > $batchInsertLength) {
            // 批量查询
            foreach ($query->batch($batchInsertLength, $this->db) as $rows) {
                $data = array_merge($data, $rows);
            }

            if (empty($data)) {
                return '';
            }

            $countData = count($data);
            for ($i = 0; $i < $countData; $i += $batchInsertLength) {
                $insertData = $this->twoArrayStyleString(array_slice($data, $i, $batchInsertLength), $tableColumns, $indent + 1);

                // $this->batchInsert({{%tableName}},
                //     [$columns],
                //     [$insertData]
                // );
                $definition .= $textIndent;
                $definition .= "\$this->batchInsert('{{%$tableName}}', " . self::ENTER;
                $definition .= $columns . ',' . self::ENTER;
                $definition .= $insertData . self::ENTER;
                $definition .= $textIndent . ');' . self::ENTER;
            }

        } else {
            $data = $query->all($this->db);

            if (empty($data)) {
                return '';
            }

            $insertData = $this->twoArrayStyleString($data, $tableColumns, $indent + 1);

            // $this->batchInsert({{%tableName}},
            //     [$columns],
            //     [$insertData]
            // );
            $definition .= $textIndent;
            $definition .= "\$this->batchInsert('{{%$tableName}}', " . self::ENTER;
            $definition .= $columns . ',' . self::ENTER;
            $definition .= $insertData . self::ENTER;
            $definition .= $textIndent . ');' . self::ENTER;
        }

        $definition .= $textIndent . '$this->_transaction->commit();' . self::ENTER;

        return $definition;

    }

    /**
     * truncate table data
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getDropTableData(TableSchema $table, $indent = 0)
    {
        // Do not use "truncate tableName", it will delete all data of table, include data before you run "./yii migrate" commond

        $definition = $this->textIndent($indent);
        $definition .= "\$this->_transaction->rollBack();";
        $definition .= self::ENTER;

        return $definition;
    }

    /**
     * get column key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getKey(TableSchema $table, $indent = 0)
    {
        $definition = '';
        $textIndent = $this->textIndent($indent);
        $tablePrefix = $this->db->tablePrefix;
        $tableName = static::removePrefix($table->name, $tablePrefix);

        $data = $this->db->createCommand('SHOW KEYS FROM ' . $table->name)->queryAll();
        if (empty($data)) {
            return '';
        }

        // 获取索引数据,包括主键
        $keyData = [];
        foreach ($data as $value) {
            $keyName = static::removePrefix($value['Key_name'], $tablePrefix);
            if (empty($keyData[$keyName])) {
                $keyData[$keyName] = [];
            }

            array_push($keyData[$keyName], [
                'table' => $value['Table'],
                'is_unique' => $value['Non_unique'] ? 0 : 1,
                'column_name' => $value['Column_name'],
                'allow_null' => $value['Null'],
                'comment' => $value['Comment'],
                'index_comment' => $value['Index_comment'],
            ]);
        }

        foreach ($keyData as $keyName => $value) {
            $columns = implode(',', array_column($value,'column_name'));
            $definition .= $textIndent;
            $definition .= "\$this->runSuccess['$keyName'] = "; // record which key add successfully

            // primary key
            if ('PRIMARY' === $keyName) {
                $definition .= "\$this->addPrimaryKey(null, '{{%$tableName}}', '$columns');" . self::ENTER;

                foreach ($keyData['PRIMARY'] as $column) {
                    $column = $column['column_name']; // table column name
                    // auto_increment
                    if ($table->columns[$column]->autoIncrement) {
                        $columnType = $table->columns[$column]->type;
                        $property = $table->columns[$column]->unsigned ? 'unsigned' : '';
                        $auto_increment = $this->getAutoIncrementNumber($table->name);
                        $definition .= $textIndent;
                        $definition .= "\$this->runSuccess['addAutoIncrement'] = ";
                        $definition .= "\$this->addAutoIncrement('{{%$tableName}}', '$column', '$columnType', '$property', $auto_increment);" . self::ENTER;
                    }
                }

            } else {
                // other keys except primary key
                $isUnique = $value[0]['is_unique'];
                $definition .= "\$this->createIndex('$keyName', '{{%$tableName}}', '$columns', {$isUnique});" . self::ENTER;
            }

        }

        return $definition;
    }

    /**
     * drop column key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getDropKey(TableSchema $table, $indent = 0)
    {
        $textIndent = $this->textIndent($indent);
        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        $definition = <<<DEFINITION
{$textIndent}foreach (\$this->runSuccess as \$keyName => \$value) {
{$textIndent}    if ('addAutoIncrement' === \$keyName) {
{$textIndent}        continue;
{$textIndent}    } elseif ('PRIMARY' === \$keyName) {
{$textIndent}        // must be remove auto_increment before drop primary key
{$textIndent}        if (isset(\$this->runSuccess['addAutoIncrement'])) {
{$textIndent}            \$value = \$this->runSuccess['addAutoIncrement'];
{$textIndent}            \$this->dropAutoIncrement("{\$value['table_name']}", \$value['column_name'], \$value['column_type'], \$value['property']);
{$textIndent}        }
{$textIndent}        \$this->dropPrimaryKey(null, '{{%{$tableName}}}');
{$textIndent}    } elseif (!empty(\$keyName)) {
{$textIndent}        \$this->dropIndex("`\$keyName`", '{{%{$tableName}}}');
{$textIndent}    }
{$textIndent}}
DEFINITION;

        return $definition . self::ENTER;

    }

    /**
     * add forengin key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getFK(TableSchema $table, $indent = 0)
    {
        if (empty($table->foreignKeys)) {
            return '';
        }

        $string = $this->db->createCommand("SHOW CREATE TABLE {$table->name}")->queryAll();
        $onParams = $this->FKOnParams($string[0]['Create Table']);

        $textIndent = $this->textIndent($indent);
        $definition = $textIndent;
        $definition .= '$tablePrefix = \Yii::$app->getDb()->tablePrefix;' . self::ENTER;
        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        foreach ($table->foreignKeys as $fkName => $fk) {
            $refTable = '';
            $refColumns = '';
            $columns = '';
            $delete = isset($onParams[$fkName]['ON DELETE']) ? "'{$onParams[$fkName]['ON DELETE']}'" : 'null';
            $update = isset($onParams[$fkName]['ON UPDATE']) ? "'{$onParams[$fkName]['ON UPDATE']}'" : 'null';
            $fkName = static::removePrefix($fkName, $this->db->tablePrefix);

            foreach ($fk as $k => $v) {
                if (0 === $k) {
                    $refTable = $v;
                } else {
                    $columns = $k;
                    $refColumns = $v;
                }
            }

            $definition .= $textIndent;
            $definition .= "\$this->runSuccess[\$tablePrefix.'{$fkName}'] = ";
            $definition .= sprintf(
                    "\$this->addForeignKey(\$tablePrefix.'%s', '{{%%%s}}', '%s', '{{%%%s}}', '%s', %s, %s);" . self::ENTER,
                    $fkName, // 外健名称
                    $tableName, // 表名
                    $columns, // 列名
                    static::removePrefix($refTable, $this->db->tablePrefix), // 对应外健的表名
                    $refColumns, // 对应外健的列名
                    $delete, // ON DELETE
                    $update // ON UPDATE
                );

        }
        return $definition;
    }

    /**
     * drop foreign key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    public function getDropFK(TableSchema $table, $indent = 0)
    {
        if (empty($table->foreignKeys)) {
            return '';
        }

        $textIndent = $this->textIndent($indent);
        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        $definition = <<<DEFINITION
{$textIndent}foreach (\$this->runSuccess as \$keyName => \$value) {
{$textIndent}    \$this->dropForeignKey(\$keyName, '{{%$tableName}}');
{$textIndent}}
DEFINITION;

        return $definition . self::ENTER;

    }

    /**
     * 获取表自动增长的起始位置
     * @param string $tableName 表名称，带前缀
     * @return int
     */
    public function getAutoIncrementNumber($tableName)
    {
        $data = $this->db->createCommand("SHOW CREATE TABLE {$tableName}")->queryAll();
        $startPosition = (int) strpos($data[0]['Create Table'], 'AUTO_INCREMENT=');
        $endPosition = (int) strpos($data[0]['Create Table'], 'DEFAULT CHARSET=');

        return $startPosition && $endPosition ? (int) substr($data[0]['Create Table'], $startPosition + 15, $endPosition - $startPosition - 1) : 0;
    }

    /**
     * return string of array style
     * eg: "[['1'], ['2'], ['3']]"
     * @param array $array 二维数组
     * @param array $columnType 字段类型
     * @param int $indent int text-indent 文本缩进
     * @return string
     */
    public function twoArrayStyleString(array $array, array $columnType, $indent = 0)
    {
        $string = $this->textIndent($indent) . '[' . self::ENTER;

        foreach ($array as $key => $value) {
            $string .= $this->oneArrayStyleString($value, $columnType, $indent + 1) . ',' . self::ENTER;
        }

        $string .= $this->textIndent($indent) . ']';

        return $string;
    }

    /**
     * return string of array style
     * eg: "['1', '2', '3']"
     * @param array $array 一维数组
     * @param array $columnType 字段类型
     * @param int $indent int text-indent 文本缩进
     * @return string
     */
    public function oneArrayStyleString(array $data, array $columnType, $indent = 0)
    {
        $string = '[';

        // get rows data
        foreach ($data as $columnName => $value) {
            if (null === $value) {
                $string .= 'null, ';
            } elseif (false !== strpos($columnType[$columnName]->dbType, 'int')) {
                $string .= (int) $value . ', ';
            } else { // string text ...
                $value = str_replace(["'", "\\", "\r\n"], ["\'", "\\", self::ENTER], $value);
                $string .= "'{$value}', ";
            }
        }

        $string = rtrim($string, ', ');
        $string .= ']';

        return $this->textIndent($indent) . $string;
    }

    /**
     * @param int $indent text-indent number
     * @return string
     */
    public function textIndent($indent)
    {
        return str_repeat($this->textIndent, $indent);
    }

    /**
     * @param $tableName string
     * @param int $indent the number of text indent
     * @return string
     */
    public function getCreateTable($tableName, $indent = 0)
    {
        $tableName = static::removePrefix($tableName, $this->db->tablePrefix);
        $textIndent = $this->textIndent($indent);

        $definition = $textIndent . '$this->runSuccess[\'createTable\'] = ';
        $definition .= "\$this->createTable('{{%$tableName}}', [" . self::ENTER;

        return $definition;
    }

    /**
     * Returns the columns definition.
     * @param $columns array
     * @param int $indent the number of text indent
     * @return string
     */
    public function getColumns(array $columns, $indent = 0)
    {
        $definition = '';
        $textIndent = $this->textIndent($indent);
        foreach ($columns as $column) {
            $tmp = sprintf("'%s' => \$this->%s%s," . self::ENTER,
                $column->name, static::getSchemaType($column), static::other($column));

            if (null !== $column->enumValues) {
                $tmp = static::replaceEnumColumn($tmp);
            }
            $definition .= $textIndent . $tmp; // 处理缩进问题
        }
        return $definition;
    }

    /**
     * Returns the primary key definition.
     * @param array $pk
     * @param array $columns
     * @param int $indent the number of text indent
     * @return string the primary key definition
     */
    public function getPrimaryKey(array $pk, array $columns, $indent = 0)
    {
        if (empty($pk)) {
            return '';
        }

        // Composite primary keys
        if (2 <= count($pk)) {
            $compositePk = implode(', ', $pk);
            return $this->textIndent($indent) . "'PRIMARY KEY ($compositePk)'," . self::ENTER;
        }
        // Primary key not an auto-increment
        $flag = false;
        foreach ($columns as $column) {
            if ($column->autoIncrement) {
                $flag = true;
            }
        }
        if (false === $flag) {
            return $this->textIndent($indent) . sprintf("'PRIMARY KEY (%s)'," . self::ENTER, $pk[0]);
        }
        return '';
    }

    /**
     * @param TableSchema $table
     * @param int $indent the number of text indent
     * @return string
     */
    public function getTableOptions(TableSchema $table, $indent = 0)
    {
        $tableComment = $this->getTableComment($table, $indent);
        $tableOptions = $this->textIndent($indent) . '], $this->tableOptions);' . self::ENTER;

        if (! empty($tableComment)) {
            $tableOptions .= self::ENTER . $tableComment . self::ENTER;
        }

        return $tableOptions;
    }

    /**
     * get table comment
     * @param TableSchema $table
     * @param int $indent the number of text indent
     * @return string
     */
    public function getTableComment(TableSchema $table, $indent = 0)
    {

        if (null === $this->_tableStatus) {
            try {
                // 不知 “SHOW TABLE STATUS” sql语句在其他数据库中是否会执行成功，所以用try catch捕获异常
                $this->_tableStatus = $this->db->createCommand('SHOW TABLE STATUS')->queryAll();
            } catch (\Exception $e) {
                return '';
            }
        }

        $textIndent = $this->textIndent($indent);
        $definition = $textIndent . '$this->runSuccess[\'addTableComment\'] = ';
        $tableName = static::removePrefix($table->name, $this->db->tablePrefix);

        foreach ($this->_tableStatus as $value) {
            if ($table->name === $value['Name'] && ! empty($value['Comment'])) {
                $comment = addslashes($value['Comment']);
                return $definition . "\$this->addCommentOnTable('{{%$tableName}}', '{$comment}');";
            }
        }

        return '';
    }

    /**
     * 获取外键ON关键字参数
     * @param $string "show create table <table-name>"
     * @return array
     */
    public function FKOnParams($string)
    {
        $matchs = [];
        $data = [];
        $pattern = "RESTRICT|CASCADE|NO ACTION|SET DEFAULT|SET NULL";

        // 匹配每一行
        preg_match_all("/CONSTRAINT `([0-9a-zA-Z_]+)` FOREIGN KEY .*/", $string, $matchs);

        foreach ($matchs[0] as $key => $fkSql) {
            if (strpos($fkSql, 'ON DELETE') !== false) {
                $match = [];
                preg_match("/ON DELETE ($pattern)/", $fkSql, $match);
                $data[$matchs[1][$key]]['ON DELETE'] = $match[1];
            }

            if (strpos($fkSql, 'ON UPDATE') !== false) {
                $match = [];
                preg_match("/ON UPDATE ($pattern)/", $fkSql, $match);
                $data[$matchs[1][$key]]['ON UPDATE'] = $match[1];
            }
        }

        return $data;
    }

    /**
     * Returns the schema type.
     * @param ColumnSchema $column
     * @return string the schema type
     */
    public static function getSchemaType(ColumnSchema $column)
    {
        // boolean
        if ('tinyint(1)' === $column->dbType) {
            return 'boolean()';
        }

        // enum
        if (null !== $column->enumValues) {
            // https://github.com/yiisoft/yii2/issues/9797
            $enumValues = array_map('addslashes', $column->enumValues);
            return "enum(['".implode('\', \'', $enumValues)."'])";
        }

        switch ($column->type) {
            case 'smallint':
                $type = 'smallInteger';
                break;

            case 'int':
                $type = 'integer';
                break;

            case 'bigint':
                $type = 'bigInteger';
                break;

            default:
                $type = $column->type;
                break;
        }

        // others
        if (null === $column->size) {
            return "$type()";
        } else {
            return "{$type}({$column->size})";
        }

    }

    /**
     * Returns the other definition.
     * @param ColumnSchema $column
     * @return string the other definition
     */
    public static function other(ColumnSchema $column)
    {
        $definition = '';

        // unsigned
        if ($column->unsigned) {
            $definition .= '->unsigned()';
        }

        // null
        if ($column->allowNull) {
            $definition .= '->null()';
        } else {
            $definition .= '->notNull()';
        }

        // default value
        if ($column->defaultValue instanceof Expression) {
            $definition .= "->defaultExpression('$column->defaultValue')";

        } elseif (is_int($column->defaultValue)) {
            $definition .= "->defaultValue($column->defaultValue)";

        } elseif (is_bool($column->defaultValue)) {
            $definition .= '->defaultValue('.var_export($column->defaultValue, true).')';

        } elseif (is_string($column->defaultValue)) {
            $definition .= "->defaultValue('".addslashes($column->defaultValue)."')";
        }

        // comment
        if (null !== $column->comment && '' !== $column->comment) {
            $definition .= "->comment('".addslashes($column->comment)."')";
        }

        // append

        return $definition;
    }

    /**
     * @param string $tmp temporary definition
     * @return string
     */
    public static function replaceEnumColumn($tmp)
    {
        return preg_replace("/,\n/", "\"," . self::ENTER, strtr($tmp, [
            '()' => '',
            '])' => ')',
            '),' => ',',
            '$this->enum([' => '"ENUM (',
            '->notNull' => ' NOT NULL',
            '->null' => ' DEFAULT NULL',
            '->defaultValue(' => ' DEFAULT ',
            '->comment(' => ' COMMENT ',
        ]));
    }

}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump\abstracts;

use Yii;
use yii\base\Component;
use yii\db\Connection;
use yii\db\TableSchema;

/**
 * Class abstractDump
 * @property Connection $db
 * @package hzhihua\dump\Models
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
abstract class AbstractSchema extends Component
{
    /**
     * @var Connection|string the DB connection object or the application component ID of the DB connection.
     */
    public $db = 'db';

    /**
     * table list
     * @param Connection $db
     * @param string $sparactor "table1, table2, table3"
     * @return string
     */
    abstract public function getTableList(Connection $db, $sparactor = ', ');

    /**
     * get table safeUp definition
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getTable(TableSchema $table, $indent = 0);

    /**
     * get table safeDown definition
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getDropTable(TableSchema $table, $indent = 0);

    /**
     * fetch table data limit(start,end)
     * @param TableSchema $table
     * @param $limit string select data from table limit ...
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getTableData(TableSchema $table, $limit = null, $indent = 0);

    /**
     * truncate table data
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getDropTableData(TableSchema $table, $indent = 0);

    /**
     * get column key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getKey(TableSchema $table, $indent = 0);

    /**
     * drop column key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getDropKey(TableSchema $table, $indent = 0);

    /**
     * add forengin key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getFK(TableSchema $table, $indent = 0);

    /**
     * drop foreign key
     * @param TableSchema $table
     * @param $indent int text-indent 文本缩进
     * @return string
     */
    abstract public function getDropFK(TableSchema $table, $indent = 0);

    /**
     * add prefix for $string
     * @param $string string
     * @param $prefix string
     * @return string
     */
    public static function addPrefix($string, $prefix)
    {
        if (empty($prefix)) {
            return $string;
        }

        return strtolower($prefix) . $string;
    }

    /**
     * remove prefix for $string
     * @param $string string
     * @param $prefix string
     * @return string
     */
    public static function removePrefix($string, $prefix = null)
    {
        if (empty($prefix)) {
            $prefix = Yii::$app->controller->db->tablePrefix ?: null;
        }

        if (strpos($string, $prefix) === false) {
            return $string;
        }

        $position = strpos($string, $prefix) + strlen($prefix);

        return substr($string, $position);
    }
}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump;

use Yii;
use yii\db\Exception;
use yii\helpers\Console;
use hzhihua\dump\models\Output;
/**
 * Migration class file.
 * all migration file generated extends this file
 * @property \yii\db\Transaction $_transaction
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
class Migration extends \yii\db\Migration
{
    /**
     * enter 换行符号
     */
    const ENTER = "\n";

    /**
     * @var string table additional options
     */
    public $tableOptions = '';

    /**
     * @var array record which sql run successfully
     */
    protected $runSuccess = [];

    /**
     * @var \yii\db\Transaction save transaction for insert table data
     */
    protected $_transaction = null;

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        if ($this->db->driverName === 'mysql') {
            // https://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
            // use utf8mb4 may cause some errors that "Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes" for "ADD UNIQUE INDEX"
            // you can use " ENGINE=InnoDB CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci " for mysql >= 5.7

            $controllerMap = Yii::$app->controllerMap;
            foreach ($controllerMap as $key => $value) {
                if (
                    isset($value['tableOptions']) &&
                    isset($value['class']) &&
                    $value['class'] === 'hzhihua\dump\DumpController'
                ) {
                    $tableOptions = sprintf(
                        ' %s ',
                        $controllerMap[$key]['tableOptions']
                    );
                    break;
                }
            }

            isset($tableOptions) || $this->tableOptions = ' ENGINE=InnoDB CHARACTER SET=utf8 COLLATE=utf8_unicode_ci ';
        } else {
            throw new Exception('Sorry, we only support mysql database.');
        }
    }

    /**
     * @return bool return true if applying success or throw new exception of db
     * @throws \yii\db\Exception
     */
    public function up()
    {
        try {
            Output::stdout('*** running safeUp' . self::ENTER, 0, Console::FG_YELLOW);
            $this->safeUp();

        } catch (Exception $e) {

            try {
                Output::stdout(self::ENTER . '*** running safeDown' . self::ENTER, 0, Console::FG_YELLOW);
                $this->safeDown();
            } catch (Exception $_e) {

            }

            Output::stdout(self::ENTER . '*** Error: ', 1, Console::FG_RED);
            throw new Exception($e->getMessage(), $e->errorInfo, 1);
        }

        return null;
    }

    /**
     * 添加自动增长
     * @param string $table 表名{{%tableName}}
     * @param string $column 字段名称
     * @param string $columnType 字段类型
     * @param boolean $property 字段属性
     * @param int $auto_increment 自动增长字段的启始值
     * @return array
     */
    public function addAutoIncrement($table, $column, $columnType, $property = null, $auto_increment = 0)
    {
        $auto_increment = (int) $auto_increment;
        $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $columnType);

        (null !== $property) && $sql .= " $property ";
        $sql .= "NOT NULL AUTO_INCREMENT";
        (0 !== $auto_increment) && $sql .= ", AUTO_INCREMENT = {$auto_increment}";

        $time = $this->beginCommand($sql);
        $this->db->createCommand()->setSql($sql)->execute();
        $this->endCommand($time);

        return [
            'table_name' => $table,
            'column_name' => $column,
            'column_type' => $columnType,
            'property' => $property,
            'auto_increment' => $auto_increment,
        ];
    }

    /**
     * 删除自动增长
     * @param string $table 表名{{%tableName}}
     * @param string $column 字段名称
     * @param string $columnType 字段类型
     * @param boolean $property 字段属性
     */
    public function dropAutoIncrement($table, $column, $columnType, $property = null)
    {
        $tip = "drop auto_increment on {$table}";
        $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $columnType);
        (null !== $property) && $sql .= " $property ";
        $sql .= "NOT NULL";

        $time = $this->beginCommand($tip);
        $this->db->createCommand()->setSql($sql)->execute();
        $this->endCommand($time);
    }

    /**
     * 删除主键
     * @param string $name
     * @param string $table
     */
    public function dropPrimaryKey($name, $table)
    {
        $tip = "drop primary key on {$table}";
        $sql =  'ALTER TABLE ' . $this->db->quoteTableName($table)
            . ' DROP PRIMARY KEY';

        $time = $this->beginCommand($tip);
        $this->db->createCommand()->setSql($sql)->execute();
        $this->endCommand($time);
    }
}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump;

use Yii;
use yii\di\Instance;
use yii\db\Connection;
use yii\db\TableSchema;
use yii\helpers\Console;
use yii\console\Controller;
use hzhihua\dump\models\Schema;

/**
 * Generate migration file from an existing database
 *
 * ## Configuration in *console/config/main.php*
 * ```
 * return [
 *     'controllerMap' => [
 *         'dump' => [
 *             'class' => 'hzhihua\\dump\\DumpController',
 *             'db' => 'db', // Connection
 *             'templateFile' => '@vendor/hzhihua/yii2-dump/templates/migration.php',
 *             'generatePath' => '@console/migrations',
 *             'table' => 'table1,table2', // select which table will be dump(default filter migration table)
 *             'filter' => 'table3,table4', // table3 and table4 will be filtered when generating migration file
 *             'limit' => '0,1000', // select * from tableName limit 0,1000
 *             // ... ...
 *         ],
 *     ],
 * ];
 * ```
 *
 * ## Default Table Options
 * > ENGINE=InnoDB CHARACTER SET=utf8 COLLATE=utf8_unicode_ci
 * it was defined at "@vendor/hzhihua/yii2-dump/src/Migration.php" file
 *
 * ## Default Limit
 * ```
 * 0,1000
 * ```
 *
 * ## Default Filter Table
 * > migration
 *
 *
 * ## Commands
 *
 * Check help
 * ```
 * ./yii help dump
 * ```
 *
 * print all over the table name without table prefix
 * ```
 * ./yii dump/list
 * ```
 *
 * generate all over the table migration file(default filter migration table)
 * ```
 * ./yii dump
 * or
 * ./yii dump/generate
 * ```
 *
 * generate all over the table migration file but it only had some data between 0 and 10
 * ```
 * ./yii dump -limit=10
 * ```
 *
 * only generate table1,table2 migration file
 * ```
 * ./yii dump -table=table1,table2
 * ```
 *
 * only generate table1,table2 migration file and only table1 will be dumped table data
 * ```
 * ./yii dump -table=table1,table2 -data=table1
 * ```
 *
 * generate all over the migration table file without table1,table2
 * ```
 * ./yii dump -filter=table1,table2
 * ```
 *
 * print all over the code of migration table file(default filter migration table)
 * ```
 * ./yii dump/create
 * ```
 *
 * Display the 'createTable' code at the terminal.
 * ```
 * ./yii dump/create
 * ```
 *
 * Display the 'dropTable' code at the terminal.
 * ```
 * ./yii dump/drop
 * ```
 *
 * -type params
 * ```
 * ./yii dump -type=0/1/2/3
 * >>> -type=0 generate table migration file,
 * >>> -type=1 generate table data migration file,
 * >>> -type=2 generate add key migration file,
 * >>> -type=3 generate add foreign key migration file
 * ```
 *
 * Useful commands (for macOS user):
 * ```
 * ./yii dump | pbcopy
 * ./yii dump/drop | pbcopy
 * ```
 * @package hzhihua\dump
 * @property \hzhihua\dump\abstracts\AbstractSchema $schema
 * @property \hzhihua\dump\abstracts\AbstractOutput $output
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
class DumpController extends Controller
{
    /**
     * @var string sparactor table name of actionList
     */
    public $sparactor = ', ';

    /**
     * @var string a migration table name without table prefix
     */
    public $migrationTable = 'migration';

    /**
     * table options for application migrate file
     * **you can use " ENGINE=InnoDB CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci " for mysql >= 5.7**
     *
     * Default: " ENGINE=InnoDB CHARACTER SET=utf8 COLLATE=utf8_unicode_ci "
     *
     * ```
     * create table `user` (
     *     ... ...
     * ) ENGINE=InnoDB CHARACTER SET=utf8 COLLATE=utf8_unicode_ci;
     * ```
     *
     * @var string
     */
    public $tableOptions = null;

    /**
     * migrate file prefix
     * default: current timestamp
     *
     * Usage:
     * ```php
     * [
     *      'class' => 'hzhihua\dump\DumpController',
     *      'filePrefix' => '123456_789012',
     * ],
     * ```
     *
     * @var null
     */
    public $filePrefix = null;

    /**
     *
     * ```
     * ./yii dump -type=0 # generate table migration file
     * ./yii dump -type=1 # generate table data migration file
     * ./yii dump -type=2 # generate add key migration file
     * ./yii dump -type=3 # generate add foreign key migration file
     * ```
     * @param int $type
     */
    public $type = null;

    /**
     * @var array i18n translation
     */
    public $translations = null;

    /**
     * 命令行参数 -filter=table1,table2 只生成table1,table2 表的Migration文件
     *
     * ```
     * ./yii dump -table=table1,table2 # generate table1,table2 migration file, include key/foreignKey, but not include dump table data
     * ./yii dump -table=table1,table2 -limit # dump table1,table2 all data, include key/foreignKey
     * ./yii dump -table=table1,table2 -data=table1 -limit # generate table1,table2 migration file, include key/foreignKey, but only *table1* will dump all table data
     * ```
     * it will only generate table1 and table2
     *
     * @var string which table will be generated
     * @default all table will be generated
     */
    public $table = null;

    /**
     * params: -data
     * ```
     * ./yii dump -data=table1,table2  # table1,table2 will be dumped all data
     * ./yii dump -data=table1,table2 -limit=0,100 # table1,table2 will be dumped data between 0 and 100
     * ./yii dump -data=table1,table2 -limit=5 # table1,table2 will be dumped data between 0 and 5
     * ./yii dump -data=table1,table2 -limit=5, # table1,table2 will be dumped data offset 5 to all
     * ```
     * @var string select tables that will be dumped some basic data
     */
    public $dumpData = null;

    /**
     * 命令行参数 -filter=table1,table2 只过滤table1,table2 数据库表
     *
     * ```
     * ./yii dump -filter=table1,table2 # it will generate all table migration file, but not include table1,table2
     * ```
     * it will not generte table1 and table2
     * @var null
     * @default filter migration,user table
     */
    public $filter = null;

    /**
     *
     * -limit= dump all data
     * @var string select * from table limit 0,1000
     */
    public $limit = '0,1000';

    /**
     * @inheritdoc
     */
    public $defaultAction = 'generate';

    /**
     * @var Connection|string the DB connection object or the application component ID of the DB connection.
     */
    public $db = 'db';

    /**
     * @var string $schema get schema table data
     */
    public $schema = 'hzhihua\dump\models\Schema';

    /**
     * @var string output table content at terminal or file
     */
    public $output = 'hzhihua\dump\models\Output';

    /**
     * @var string the directory of generating migrate file
     */
    public $generateFilePath = '@console/migrations';

    /**
     * @var string the directory of migrate template file
     */
    public $templateFile = '@vendor/hzhihua/yii2-dump/templates/migration.php';

    /**
     * change the order of applying migration file
     * but the value could not be changed
     * you only can change the key that influence the order of applying migration file
     * eg: configuration below
     * >>> first, generate: m123456_123456_0_table_tableName.php
     * >>> second, generate: m123456_123456_1_tableData_tableName.php
     * >>> third, generate: m123456_123456_2_key_tableName.php
     * >>> fourth, generate: m123456_123456_3_FK_tableName.php
     * so,
     * ```
     * ./yii migrate
     * ```
     * it will first apply the migration file that its name is "m123456_123456_0_table_tableName.php"
     * ... ...
     * the migration file will be apply at last time with the name of "m123456_123456_3_FK_tableName.php"
     * @var array
     */
    public $applyFileOrder = [
        0 => 'table',
        1 => 'tableData',
        2 => 'key',
        3 => 'FK', // foreign key
    ];

    /**
     * 记录所有已经 过滤 的数据库表，不带表前缀
     * collect all over the table of filter without table prefix
     * eg: ['table1', 'table2']
     *
     * @var array
     */
    protected $filters = [];

    /**
     * 记录所有已经 生成 的数据库表，不带表前缀
     * collect all over the table of generation without table prefix
     * eg: ['table1', 'table2']
     *
     * @var array
     */
    protected $generations = [];

    /**
     * @inheritdoc
     */
    public function options($actionID)
    {
        return array_merge(parent::options($actionID), [
            'db',
            'table',  // database table
            'filter', // filter database table
            'limit', // select data from table limit ...
            'type',
            'dumpData',
            'migrationTable',
            'templateFile' => 'templateFile',
            'generateFilePath' => 'generateFilePath',
        ]);
    }

    /**
     * @inheritdoc
     */
    public function optionAliases()
    {
        return array_merge(parent::optionAliases(), [
            'table' => 'table',
            'filter' => 'filter',
            'limit' => 'limit',
            'type' => 'type',
            'data' => 'dumpData', // which table will be dump data
            'templateFile' => 'templateFile',
            'migrationTable' => 'migrationTable',
            'generateFilePath' => 'generateFilePath',
        ]);
    }

    public function beforeAction($action)
    {
        if (!parent::beforeAction($action)) {
            return false;
        }

        $schema = $this->schema;
        $this->schema = Instance::ensure($this->schema, Schema::class);
        $this->db = Instance::ensure($this->db, Connection::class);
        $this->schema->db = isset($schema['db']) ? Instance::ensure($schema['db'], Connection::class) : $this->db;
        $this->output = Instance::ensure($this->output, 'hzhihua\dump\Models\Output');

        Yii::$app->i18n->translations['dump*'] =
            $this->translations ?: [
                'class' => 'yii\i18n\PhpMessageSource',
                'basePath' => __DIR__ . '/../messages',
                'fileMap' => [
                    'dump' => 'main.php',
                ],
            ];

        return true;
    }

    /**
     * print all over the table name without table prefix
     *
     * @return int the status of the action execution
     */
    public function actionList()
    {
        $tableList = $this->schema->getTableList($this->db, $this->sparactor) . "\n";
        return $this->output->stdout($tableList, 0, Console::FG_YELLOW);
    }

    /**
     * generate migration file -type = 0 generate table, 1 generate table data, 2 generate add key, 3 generate add foreign key
     * @return int the status of the action execution
     */
    public function actionGenerate()
    {
        $this->output->startPrintf('Process');
        $tableOptions = $this->getOptions($this->table);
        $filterOptions = $this->getOptions($this->filter);
        $info = Yii::t('dump', 'Generate Migration File');

        foreach ($this->db->getSchema()->getTableSchemas() as $table) {
            // filter some table  -filter=...  -table=...
            if ($this->filterTable($table->name, $filterOptions, $tableOptions)) {
                continue;
            }

            switch ($this->type) {
                case '0':
                    $this->generateFile($table, 'table', $info, 2);
                    break;

                case '1':
                    $this->generateFile($table, 'tableData', $info, 2);
                    break;

                case '2':
                    $this->generateFile($table, 'key', $info, 2);
                    break;

                case '3':
                    $this->generateFile($table, 'FK', $info, 2);
                    break;

                default:
                    $this->generateFile($table, 'table', $info, 2);
                    $this->generateFile($table, 'tableData', $info, 2);
                    $this->generateFile($table, 'key', $info, 2);
                    $this->generateFile($table, 'FK', $info, 2);
                    break;
            }

        }

        $this->output->conclusion($this->generations, $this->filters);
        $this->output->endPrintf('Process');
        return 0;
    }

    /**
     * print the 'createTable' code.
     *
     * @return int the status of the action execution
     */
    public function actionCreate()
    {
        $this->output->startPrintf('Process');
        $tableOptions = $this->getOptions($this->table);
        $filterOptions = $this->getOptions($this->filter);

        $createTableInfo = Yii::t('dump', 'Create Table');
        $insertDataInfo = Yii::t('dump', 'Insert Data Of Table');
        $addKeyInfo = Yii::t('dump', 'Add Key Of Table');
        $addFKInfo = Yii::t('dump', 'Add Foreign Key Of Table');

        foreach ($this->db->getSchema()->getTableSchemas() as $table) {
            // filter some table  -filter=...  -table=...
            if ($this->filterTable($table->name, $filterOptions, $tableOptions)) {
                continue;
            }

            switch ($this->type) {
                case '0':
                    $this->printf($table, 'table', $createTableInfo);
                    break;

                case '1':
                    $this->printf($table, 'tableData', $insertDataInfo);
                    break;

                case '2':
                    $this->printf($table, 'key', $addKeyInfo);
                    break;

                case '3':
                    $this->printf($table, 'FK', $addFKInfo); // foreign key
                    break;

                default:
                    $this->printf($table, 'table', $createTableInfo);
                    $this->printf($table, 'tableData', $insertDataInfo);
                    $this->printf($table, 'key', $addKeyInfo);
                    $this->printf($table, 'FK', $addFKInfo); // foreign key
                    break;
            }

        }

        $this->output->conclusion($this->generations, $this->filters);
        $this->output->endPrintf('Process');
        return 0;
    }

    /**
     * print the 'dropTable' code.
     *
     * @return integer the status of the action execution
     */
    public function actionDrop()
    {
        $this->output->startPrintf('Process');
        $tableOptions = $this->getOptions($this->table);
        $filterOptions = $this->getOptions($this->filter);

        $dropTableInfo = Yii::t('dump', 'Drop Table');
        $dropDataInfo = Yii::t('dump', 'Drop Data Of Table');
        $dropKeyInfo = Yii::t('dump', 'Drop Key Of Table');
        $dropFKInfo = Yii::t('dump', 'Drop Foreign Key Of Table');

        foreach ($this->db->getSchema()->getTableSchemas() as $table) {
            // filter some table  -filter=...  -table=...
            if ($this->filterTable($table->name, $filterOptions, $tableOptions)) {
                continue;
            }

            switch ($this->type) {
                case '0':
                    $this->printf($table, 'dropTable', $dropTableInfo);
                    break;

                case '1':
                    $this->printf($table, 'dropTableData', $dropDataInfo);
                    break;

                case '2':
                    $this->printf($table, 'dropKey', $dropKeyInfo);
                    break;

                case '3':
                    $this->printf($table, 'dropFK', $dropFKInfo); // foreign key
                    break;

                default:
                    $this->printf($table, 'dropTable', $dropTableInfo);
                    $this->printf($table, 'dropTableData', $dropDataInfo);
                    $this->printf($table, 'dropKey', $dropKeyInfo);
                    $this->printf($table, 'dropFK', $dropFKInfo); // foreign key
                    break;
            }

        }

        $this->output->conclusion($this->generations, $this->filters);
        $this->output->endPrintf('Process');
        return 0;
    }

    /**
     * Unified generate file interface
     * @param TableSchema $table
     * @param string $functionName abstract function name
     * @see \hzhihua\dump\abstracts\AbstractSchema abstract function name
     * @param null $tip
     * @param int $indent text-indent 文本缩进
     * @return bool
     */
    public function generateFile(TableSchema $table, $functionName, $tip = null, $indent = 0)
    {
        $params = $this->getParams($table, $functionName, $indent);

        if (
            empty($params['safeUp'])   ||
            empty($params['safeDown']) ||
            empty($params['className'])
        ) {
            return false;
        }

        $template = Yii::getAlias($this->templateFile);
        $outputFile = Yii::getAlias($this->generateFilePath) . DIRECTORY_SEPARATOR . $params['className'] . '.php';

        if (! empty($tip)) {
            $this->output->startPrintf($tip); // record start time
            $this->output->stdout($outputFile . "\n");
            $rst = $this->output->generateFile($params, $template, $outputFile);
            $this->output->endPrintf($tip); // record end time
        } else {
            $this->output->stdout($outputFile);
            $rst = $this->output->generateFile($params, $template, $outputFile);
        }

        return $rst;
    }

    /**
     * check
     * ```
     * ./yii dump -limit # generate all table file and dump all data beside migration table
     * ./yii dump -table=table1,table2 -limit # dump and generate all data of table1,table2
     * ./yii dump -table=table1,table2 -data=table1 -limit # generate table1,table2 migration file and dump all data of table1
     * ./yii dump -filter=table1,table2 -limit # generate migration file beside table1,table2 and dump all data beside table1,table2
     * ./yii dump -filter=table1,table2 -data=table3 -limit # generate migration file beside table1,table2 and dump all data of table3
     * ```
     * @param $tableName
     * @return bool|string
     */
    public function checkLimit($tableName)
    {
        if (null !== $this->limit) {
            if (! empty($this->dumpData)) {
                $dumpData = $this->getOptions($this->dumpData);
                if (! in_array($tableName, $dumpData)) {
                    return false;
                }
            }
            return $this->limit;
        }

        return false;
    }

    /**
     * Unified printf interface
     * @param TableSchema $table
     * @param $functionName string $functionName abstract function name
     * @see \hzhihua\dump\abstracts\AbstractSchema abstract function name
     * @param null $tip
     * @param int $indent text-indent 文本缩进
     * @return bool|int
     */
    public function printf(TableSchema $table, $functionName, $tip = null, $indent = 0)
    {
        $params = [];
        $params[] = &$table;
        $params[] = $indent;
        $functionName = ucfirst($functionName);
        $functionName = 'get' . $functionName;

        if ('getTableData' === $functionName) {
            if ($limit = $this->checkLimit($table->name)) {
                array_pop($params);
                $params[] = $limit;
                $params[] = $indent;
            } else {
                return 0;
            }
        }

        if (! $data = call_user_func_array([$this->schema, $functionName], $params)) {
            return 0;
        }

        if (! empty($tip)) {
            $tip .= ': ' . $this->schema->removePrefix($table->name, $this->db->tablePrefix);
            $this->output->startPrintf($tip); // record start time
            $this->output->stdout($data);
            $this->output->endPrintf($tip); // record end time
        } else {
            $this->output->stdout($data);
        }

        return 0;
    }

    /**
     * change order of applying file
     * 改变"./yii migrate"应用migration文件的顺序
     *
     * @param $functionName
     * @return false|int
     */
    public function changeOrderOfApplyingFile($functionName)
    {
        return array_search($functionName, $this->applyFileOrder);
    }

    /**
     * Get relevant data with array style for generating file function
     * @param TableSchema $table
     * @param string $functionName abstract function name
     * @see \hzhihua\dump\abstracts\AbstractSchema abstract function name
     * @param int $indent text-indent 文本缩进
     * @return array
     */
    public function getParams(TableSchema $table, $functionName, $indent = 0)
    {
        $params = [];
        $params[] = &$table;
        $params[] = $indent;
        $ucFunctionName = ucfirst($functionName);
        $get = 'get' . $ucFunctionName;
        $drop = 'getDrop' . $ucFunctionName;

        $order = $this->changeOrderOfApplyingFile($functionName);

        if ('getTableData' === $get) {
            if ($limit = $this->checkLimit($table->name)) {
                array_pop($params);
                $params[] = $limit;
                $params[] = $indent;
            } else {
                return [];
            }
        }

        $safeUp = call_user_func_array([$this->schema, $get], $params);
        if (3 === count($params)) {
            unset($params[1]); // unser $this->limit
        }
        $safeDown = call_user_func_array([$this->schema, $drop], $params);
        $tableName = $this->schema->removePrefix($table->name, $this->db->tablePrefix);

        return [
            'safeUp' => $safeUp ? "\n" . $safeUp . "\n" : '',
            'safeDown' => $safeDown ? "\n" . $safeDown . "\n" : '',
            'className' => static::getClassName($order . '_' . $functionName . '_' . $tableName, $this->filePrefix),
        ];

    }

    /**
     * ```
     * ./yii dump -table=table1,table2
     * ```
     * >>> return ['tablePrefix_table1', 'tablePrefix_table2'];
     *
     * ```
     * ./yii dump -filter=table1,table2
     * ```
     * >>> return ['tablePrefix_table1', 'tablePrefix_table2'];
     *
     * add tablePrefix for options
     * @param $options string options params  eg: $options=table1,table2
     * @return array
     */
    public function getOptions($options)
    {
        if (empty($options)) {
            return [];
        }

        $tablePrefixOptionsArray = []; // add table prefix
        $optionsArray = explode(',', $options);

        foreach ($optionsArray as $key => $tableName) {
            $tablePrefixOptionsArray[$key] = $this->schema->addPrefix($tableName, $this->db->tablePrefix);
        }

        return $tablePrefixOptionsArray;
    }

    /**
     * which table will not be generated
     * ```
     * ./yii dump -table=table,table1 -filter=table2,table3
     * ```
     * @param $tableName string with prefix table name
     * @param $filterTable array -filter params
     * @param $optionsTable array -table params
     * @return boolean false not filter | true filter
     */
    public function filterTable($tableName, array $filterTable = [], array $optionsTable = [])
    {

        if (empty($filterTable) && empty($optionsTable)) {
            // filter migration table
            if ($tableName !== $this->schema->addPrefix($this->migrationTable, $this->db->tablePrefix)) {

                $tableName = $this->schema->removePrefix($tableName, $this->db->tablePrefix);
                if (! in_array($tableName, $this->generations) ) {
                    // 记录所有已经 生成 的数据库表
                    $this->generations[] = $tableName;
                }
                return false;
            }

        } elseif (! empty($filterTable) && empty($optionsTable)) {
            if (! in_array($tableName, $filterTable)) {

                $tableName = $this->schema->removePrefix($tableName, $this->db->tablePrefix);
                if (! in_array($tableName, $this->generations) ) {
                    // 记录所有已经 生成 的数据库表
                    $this->generations[] = $tableName;
                }
                return false;
            }

        } elseif (empty($filterTable) && ! empty($optionsTable)) {
            if (in_array($tableName, $optionsTable)) {

                $tableName = $this->schema->removePrefix($tableName, $this->db->tablePrefix);
                if (! in_array($tableName, $this->generations) ) {
                    // 记录所有已经 生成 的数据库表
                    $this->generations[] = $tableName;
                }
                return false;
            }

        } else { // ! empty($filterTable) && ! empty($optionsTable)
            if (in_array($tableName, $optionsTable) && ! in_array($tableName, $filterTable)) {

                $tableName = $this->schema->removePrefix($tableName, $this->db->tablePrefix);
                if (! in_array($tableName, $this->generations) ) {
                    // 记录所有已经 生成 的数据库表
                    $this->generations[] = $tableName;
                }

                return false;
            }
        }

        // 记录所有已经 过滤 的数据库表
        $tableName = $this->schema->removePrefix($tableName, $this->db->tablePrefix);
        if (! in_array($tableName, $this->filters) ) {
            $this->filters[] = $tableName;
        }

        return true; // filter
    }

    /**
     * @param string $classDesc class name description
     * @return string
     */
    public static function getClassName($classDesc, $filePrefix = null)
    {
        if ($filePrefix) {
            return sprintf(
                "m%s_%s",
                $filePrefix,
                $classDesc
            );
        }

        $time = $_SERVER['REQUEST_TIME'];

        // 文件名必须以"m123456_123456_"开头
        return sprintf(
            "m%s_%s_%s",
            date('ymd', $time),
            date('His', $time),
            $classDesc
        );
    }

}


<?php
/**
 * @Author: Hzhihua
 * @Date: 17-9-7 12:18
 * @Email cnzhihua@gmail.com
 */

namespace hzhihua\dump\models;

use Yii;
use yii\base\View;
use hzhihua\dump\abstracts\AbstractOutput;
use hzhihua\dump\exceptions\CouldNotTouchFileException;
use hzhihua\dump\exceptions\CouldNotMkdirDirectoryException;
use yii\helpers\Console;

/**
 * Class Output
 * @package hzhihua\dump\models
 * @Author Hzhihua <cnzhihua@gmail.com>
 */
class Output extends AbstractOutput
{
    /**
     * enter 换行符号
     */
    const ENTER = "\n";

    /**
     * record start time with float
     * @var array
     */
    protected $startTime = [];

    /**
     * record end time with float
     * @var array
     */
    protected $endTime = [];

    /**
     * @param $content array the parameters (name-value pairs) that will be extracted and made available in the view file.
     * @see \yii\base\View::renderPhpFile()
     * @param $template string template file path
     * @param $outputFile string generate file path
     * @return bool
     * @throws CouldNotMkdirDirectoryException
     * @throws CouldNotTouchFileException
     */
    public function generateFile(array $content, $template, $outputFile)
    {
        if (
            empty($content['safeUp'])   ||
            empty($content['safeDown']) ||
            empty($content['className'])
        ) {
            return false;
        }

        $generatePath = dirname($outputFile);

        if (! is_dir($generatePath)) {
            if (mkdir($generatePath, 0755, true)) {
                throw new CouldNotMkdirDirectoryException($generatePath);
            }
        }

        $migrateContent = (new View())->renderPhpFile($template, $content);

        if (! file_put_contents($outputFile, $migrateContent)) {
            throw new CouldNotTouchFileException($outputFile);
        }

        return true;
    }

    /**
     * printf some thing at the terminal and record start time
     * @param string $string
     * @return int exit code
     */
    public function startPrintf($string)
    {
        if (empty($string)) {
            return '';
        }

        $this->startTime[$string] = microtime(true);

        $this->stdout('/*** Begin '. $string . " ***/" . self::ENTER, 0, Console::FG_YELLOW);

        return 0;
    }

    /**
     * printf some thing at the terminal and record end time
     * @param string $string
     * @return int exit code
     */
    public function endPrintf($string)
    {
        if (empty($string)) {
            return '';
        }
        $this->endTime[$string] = microtime(true);
        $time = $this->endTime[$string] - $this->startTime[$string];

        $this->stdout(
            '/*** End ' . $string . ' ... done (time: ' . sprintf("%.3f", $time) . "s) ***/" . self::ENTER . self::ENTER,
                0,
                Console::FG_GREEN
            );

        return 0;
    }

    /**
     * print conclusion at the terminal
     * @param $handleTable array all table name that had been generate
     * @param $filterTable array all table name that had been filter
     */
    public function conclusion($handleTable, $filterTable)
    {
        $enter = self::ENTER;
        $handleNumber = count($handleTable);
        $filterNumber = count($filterTable);
        $handleTableString = implode($handleTable, ', ');
        $filterTableString = implode($filterTable, ', ');
        $tables = Yii::t('dump', 'Tables');
        $handle = Yii::t('dump', 'Handle');
        $filter = Yii::t('dump', 'Filter');

        $header = <<<HEADER
/**********************************/
/************ Conclusion **********/
/**********************************/

HEADER;

        $footer = <<<FOOTER

/************ Conclusion *********/$enter
FOOTER;

        $handle = <<<HANDLE
/*** $handle $handleNumber $tables: */
>>> $handleTableString$enter
HANDLE;

        $filter = <<<FILTER
/*** $filter $filterNumber $tables: */
>>> $filterTableString
FILTER;

        $this->stdout($header, 0, Console::FG_YELLOW);
        $this->stdout($handle, Console::BOLD, Console::FG_YELLOW);
        $this->stdout($filter, Console::BOLD, Console::FG_YELLOW);
        $this->stdout($footer, 0, Console::FG_GREEN);
    }

}


<?php

/**
 * The MIT License
 *
 * Copyright 2014 Anton Raharja <antonrd at gmail dot com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
namespace Playsms;

/**
 * playSMS Webservices
 *
 * @author Anton Raharja
 */
class Webservices {

	// playSMS Webservices operation results
	private $webservices_url;
	private $response;
	private $data;
	private $status;
	private $error;
	private $error_string;

	// playSMS Webservices operation parameters
	public $url;
	public $token;
	public $username;
	public $password;
	public $operation;
	public $from;
	public $to;
	public $footer;
	public $nofooter;
	public $msg;
	public $schedule;
	public $type;
	public $unicode;
	public $queue;
	public $src;
	public $dst;
	public $datetime;
	public $smslog_id;
	public $last_smslog_id;
	public $count;
	public $keyword;

	/**
	 * Fetch content from URL
	 *
	 * @param string $query_string
	 *        Webservices URL
	 */
	private function _Fetch() {
		$this->_setWebservicesUrl();

		$response = @file_get_contents($this->getWebservicesUrl());
		$this->response = $response;
	}

	/**
	 * Process and populate class for results
	 */
	private function _Populate() {
		$this->data = json_decode($this->response);

		// getStatus() FALSE upon receiving status ERR or a non-zero error
		// else set as TRUE
		if (is_array($this->data->data)) {
			$this->status = TRUE;
		} else {
			if (isset($this->data->status) && isset($this->data->error)) {
				if (($this->data->status == 'ERR') || ((int) $this->data->error > 0)) {
					$this->status = FALSE;
				} else {
					$this->status = TRUE;
				}
			} else {
				$this->status = FALSE;
			}
		}

		if (isset($this->data->error)) {
			$this->error = ((int) $this->data->error > 0 ? (int) $this->data->error : 0);
		} else {
			$this->error = 0;
		}

		if (isset($this->data->error_string)) {
			$this->error_string = $this->data->error_string;
		} else {
			$this->error_string = '';
		}
	}

	/**
	 * Build a complete webservices URL
	 */
	private function _setWebservicesUrl() {
		$ws_url = $this->url . '&format=json';

		if ($this->token) {
			$ws_url .= '&h=' . $this->token;
		}

		if ($this->username) {
			$ws_url .= '&u=' . $this->username;
		}

		if ($this->password) {
			$ws_url .= '&p=' . $this->password;
		}

		if ($this->operation) {
			$ws_url .= '&op=' . $this->operation;
		}

		if ($this->from) {
			$ws_url .= '&from=' . urlencode($this->from);
		}

		if ($this->to) {
			$ws_url .= '&to=' . urlencode($this->to);
		}

		if ($this->footer) {
			$ws_url .= '&footer=' . urlencode($this->footer);
		}

		if ($this->nofooter) {
			$ws_url .= '&nofooter=' . $this->nofooter;
		}

		if ($this->msg) {
			$ws_url .= '&msg=' . urlencode($this->msg);
		}

		if ($this->schedule) {
			$ws_url .= '&schedule=' . $this->schedule;
		}

		if ($this->type) {
			$ws_url .= '&type=' . $this->type;
		}

		if ($this->unicode) {
			$ws_url .= '&unicode=' . $this->unicode;
		}

		if ($this->queue) {
			$ws_url .= '&queue=' . $this->queue;
		}

		if ($this->src) {
			$ws_url .= '&src=' . urlencode($this->src);
		}

		if ($this->dst) {
			$ws_url .= '&dst=' . urlencode($this->dst);
		}

		if ($this->datetime) {
			$ws_url .= '&dt=' . $this->datetime;
		}

		if ($this->smslog_id) {
			$ws_url .= '&smslog_id=' . $this->smslog_id;
		}

		if ($this->last_smslog_id) {
			$ws_url .= '&last=' . $this->last_smslog_id;
		}

		if ($this->count) {
			$ws_url .= '&c=' . $this->count;
		}

		if ($this->keyword) {
			$ws_url .= '&kwd=' . urlencode($this->keyword);
		}

		$this->webservices_url = $ws_url;
	}

	/**
	 * Get current webservices URL
	 *
	 * @return string
	 */
	public function getWebservicesUrl() {
		$this->_setWebservicesUrl();

		return $this->webservices_url;
	}

	/**
	 * Get raw response data
	 *
	 * @return string
	 */
	public function getResponse() {
		return $this->response;
	}

	/**
	 * Get JSON decoded data as an object
	 *
	 * @return object
	 */
	public function getData() {
		return $this->data;
	}

	/**
	 * Get status
	 *
	 * @return boolean
	 */
	public function getStatus() {
		return $this->status;
	}

	/**
	 * Get error code
	 *
	 * @return integer
	 */
	public function getError() {
		return $this->error;
	}

	/**
	 * Get error string
	 *
	 * @return string
	 */
	public function getErrorString() {
		return $this->error_string;
	}

	// playSMS Webservices operations

	/**
	 * Get webservices token.
	 * This operation can also be used as a login mechanism.
	 *
	 * @return mixed
	 */
	public function getToken() {
		$this->operation = 'get_token';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Set new webservices token.
	 * This operation can also be used as a change password/token mechanism.
	 *
	 * @return mixed
	 */
	public function setToken() {
		$this->operation = 'set_token';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get user's credit
	 *
	 * @return mixed
	 */
	public function getCredit() {
		$this->operation = 'cr';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Send SMS to mobile numbers
	 *
	 * @return mixed
	 */
	public function sendSms() {
		$this->operation = 'pv';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Send SMS to groups
	 *
	 * @return mixed
	 */
	public function sendSmsToGroup() {
		$this->operation = 'bc';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of outgoing SMS and delivery statuses
	 *
	 * @return mixed
	 */
	public function getOutgoing() {
		$this->operation = 'ds';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of incoming SMS
	 *
	 * @return mixed
	 */
	public function getIncoming() {
		$this->operation = 'in';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of SMS in user's inbox
	 *
	 * @return mixed
	 */
	public function getInbox() {
		$this->operation = 'ix';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of SMS in Sandbox
	 *
	 * @return mixed
	 */
	public function getSandbox() {
		$this->operation = 'sx';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of phonebook contacts
	 *
	 * @return mixed
	 */
	public function getPhonebookContacts() {
		$this->operation = 'get_contact';
		$this->_Fetch();
		return $this->_Populate();
	}

	/**
	 * Get list of phonebook groups
	 *
	 * @return mixed
	 */
	public function getPhonebookGroups() {
		$this->operation = 'get_contact_group';
		$this->_Fetch();
		return $this->_Populate();
	}
}