Paul Klimov
10 years ago
5 changed files with 496 additions and 0 deletions
@ -0,0 +1,389 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\sphinx\gii\model; |
||||
|
||||
use Yii; |
||||
use yii\sphinx\ActiveRecord; |
||||
use yii\sphinx\Connection; |
||||
use yii\sphinx\Schema; |
||||
use yii\gii\CodeFile; |
||||
use yii\helpers\Inflector; |
||||
|
||||
/** |
||||
* This generator will generate one or multiple ActiveRecord classes for the specified Sphinx index. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Generator extends \yii\gii\Generator |
||||
{ |
||||
public $db = 'sphinx'; |
||||
public $ns = 'app\models'; |
||||
public $indexName; |
||||
public $modelClass; |
||||
public $baseClass = 'yii\sphinx\ActiveRecord'; |
||||
public $useIndexPrefix = false; |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return 'Sphinx Model Generator'; |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function getDescription() |
||||
{ |
||||
return 'This generator generates an ActiveRecord class for the specified Sphinx index.'; |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function rules() |
||||
{ |
||||
return array_merge(parent::rules(), [ |
||||
[['db', 'ns', 'indexName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'], |
||||
[['ns'], 'filter', 'filter' => function($value) { return trim($value, '\\'); }], |
||||
|
||||
[['db', 'ns', 'indexName', 'baseClass'], 'required'], |
||||
[['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], |
||||
[['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], |
||||
[['indexName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], |
||||
[['db'], 'validateDb'], |
||||
[['ns'], 'validateNamespace'], |
||||
[['indexName'], 'validateIndexName'], |
||||
[['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], |
||||
[['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], |
||||
[['enableI18N'], 'boolean'], |
||||
[['useIndexPrefix'], 'boolean'], |
||||
[['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function attributeLabels() |
||||
{ |
||||
return array_merge(parent::attributeLabels(), [ |
||||
'ns' => 'Namespace', |
||||
'db' => 'Sphinx Connection ID', |
||||
'indexName' => 'Index Name', |
||||
'modelClass' => 'Model Class', |
||||
'baseClass' => 'Base Class', |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function hints() |
||||
{ |
||||
return array_merge(parent::hints(), [ |
||||
'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., <code>app\models</code>', |
||||
'db' => 'This is the ID of the Sphinx application component.', |
||||
'indexName' => 'This is the name of the Sphinx index that the new ActiveRecord class is associated with, e.g. <code>post</code>. |
||||
The index name may end with asterisk to match multiple table names, e.g. <code>idx_*</code> |
||||
will match indexes, which name starts with <code>idx_</code>. In this case, multiple ActiveRecord classes |
||||
will be generated, one for each matching index name; and the class names will be generated from |
||||
the matching characters. For example, index <code>idx_post</code> will generate <code>Post</code> |
||||
class.', |
||||
'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain |
||||
the namespace part as it is specified in "Namespace". You do not need to specify the class name |
||||
if "Index Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', |
||||
'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', |
||||
'useIndexPrefix' => 'This indicates whether the index name returned by the generated ActiveRecord class |
||||
should consider the <code>tablePrefix</code> setting of the Sphinx connection. For example, if the |
||||
index name is <code>idx_post</code> and <code>tablePrefix=idx_</code>, the ActiveRecord class |
||||
will return the table name as <code>{{%post}}</code>.', |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function autoCompleteData() |
||||
{ |
||||
$db = $this->getDbConnection(); |
||||
if ($db !== null) { |
||||
return [ |
||||
'indexName' => function () use ($db) { |
||||
return $db->getSchema()->getIndexNames(); |
||||
}, |
||||
]; |
||||
} else { |
||||
return []; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function requiredTemplates() |
||||
{ |
||||
return ['model.php']; |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function stickyAttributes() |
||||
{ |
||||
return array_merge(parent::stickyAttributes(), ['ns', 'db', 'baseClass']); |
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function generate() |
||||
{ |
||||
$files = []; |
||||
$db = $this->getDbConnection(); |
||||
foreach ($this->getIndexNames() as $indexName) { |
||||
$className = $this->generateClassName($indexName); |
||||
$indexSchema = $db->getIndexSchema($indexName); |
||||
$params = [ |
||||
'indexName' => $indexName, |
||||
'className' => $className, |
||||
'indexSchema' => $indexSchema, |
||||
'labels' => $this->generateLabels($indexSchema), |
||||
'rules' => $this->generateRules($indexSchema), |
||||
]; |
||||
$files[] = new CodeFile( |
||||
Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php', |
||||
$this->render('model.php', $params) |
||||
); |
||||
} |
||||
|
||||
return $files; |
||||
} |
||||
|
||||
/** |
||||
* Generates the attribute labels for the specified table. |
||||
* @param \yii\db\TableSchema $table the table schema |
||||
* @return array the generated attribute labels (name => label) |
||||
*/ |
||||
public function generateLabels($table) |
||||
{ |
||||
$labels = []; |
||||
foreach ($table->columns as $column) { |
||||
if (!strcasecmp($column->name, 'id')) { |
||||
$labels[$column->name] = 'ID'; |
||||
} else { |
||||
$label = Inflector::camel2words($column->name); |
||||
if (strcasecmp(substr($label, -3), ' id') === 0) { |
||||
$label = substr($label, 0, -3) . ' ID'; |
||||
} |
||||
$labels[$column->name] = $label; |
||||
} |
||||
} |
||||
|
||||
return $labels; |
||||
} |
||||
|
||||
/** |
||||
* Generates validation rules for the specified index. |
||||
* @param \yii\sphinx\IndexSchema $index the index schema |
||||
* @return array the generated validation rules |
||||
*/ |
||||
public function generateRules($index) |
||||
{ |
||||
$types = []; |
||||
foreach ($index->columns as $column) { |
||||
if ($column->isMva) { |
||||
$types['safe'][] = $column->name; |
||||
continue; |
||||
} |
||||
if ($column->isPrimaryKey) { |
||||
$types['required'][] = $column->name; |
||||
$types['unique'][] = $column->name; |
||||
} |
||||
switch ($column->type) { |
||||
case Schema::TYPE_PK: |
||||
case Schema::TYPE_INTEGER: |
||||
case Schema::TYPE_BIGINT: |
||||
$types['integer'][] = $column->name; |
||||
break; |
||||
case Schema::TYPE_BOOLEAN: |
||||
$types['boolean'][] = $column->name; |
||||
break; |
||||
case Schema::TYPE_FLOAT: |
||||
$types['number'][] = $column->name; |
||||
break; |
||||
case Schema::TYPE_TIMESTAMP: |
||||
$types['safe'][] = $column->name; |
||||
break; |
||||
default: // strings |
||||
$types['string'][] = $column->name; |
||||
} |
||||
} |
||||
$rules = []; |
||||
foreach ($types as $type => $columns) { |
||||
$rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; |
||||
} |
||||
|
||||
return $rules; |
||||
} |
||||
|
||||
/** |
||||
* Validates the [[db]] attribute. |
||||
*/ |
||||
public function validateDb() |
||||
{ |
||||
if (!Yii::$app->has($this->db)) { |
||||
$this->addError('db', 'There is no application component named "' . $this->db . '".'); |
||||
} elseif (!Yii::$app->get($this->db) instanceof Connection) { |
||||
$this->addError('db', 'The "' . $this->db . '" application component must be a Sphinx connection instance.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Validates the [[ns]] attribute. |
||||
*/ |
||||
public function validateNamespace() |
||||
{ |
||||
$this->ns = ltrim($this->ns, '\\'); |
||||
$path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false); |
||||
if ($path === false) { |
||||
$this->addError('ns', 'Namespace must be associated with an existing directory.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Validates the [[modelClass]] attribute. |
||||
*/ |
||||
public function validateModelClass() |
||||
{ |
||||
if ($this->isReservedKeyword($this->modelClass)) { |
||||
$this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); |
||||
} |
||||
if (substr($this->indexName, -1) !== '*' && $this->modelClass == '') { |
||||
$this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Validates the [[indexName]] attribute. |
||||
*/ |
||||
public function validateIndexName() |
||||
{ |
||||
if (strpos($this->indexName, '*') !== false && substr($this->indexName, -1) !== '*') { |
||||
$this->addError('indexName', 'Asterisk is only allowed as the last character.'); |
||||
|
||||
return; |
||||
} |
||||
$tables = $this->getIndexNames(); |
||||
if (empty($tables)) { |
||||
$this->addError('indexName', "Table '{$this->indexName}' does not exist."); |
||||
} else { |
||||
foreach ($tables as $table) { |
||||
$class = $this->generateClassName($table); |
||||
if ($this->isReservedKeyword($class)) { |
||||
$this->addError('indexName', "Table '$table' will generate a class which is a reserved PHP keyword."); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private $_indexNames; |
||||
private $_classNames; |
||||
|
||||
/** |
||||
* @return array the index names that match the pattern specified by [[indexName]]. |
||||
*/ |
||||
protected function getIndexNames() |
||||
{ |
||||
if ($this->_indexNames !== null) { |
||||
return $this->_indexNames; |
||||
} |
||||
$db = $this->getDbConnection(); |
||||
if ($db === null) { |
||||
return []; |
||||
} |
||||
$indexNames = []; |
||||
if (strpos($this->indexName, '*') !== false) { |
||||
$indexNames = $db->getSchema()->getIndexNames(); |
||||
} elseif (($index = $db->getIndexSchema($this->indexName, true)) !== null) { |
||||
$indexNames[] = $this->indexName; |
||||
$this->_classNames[$this->indexName] = $this->modelClass; |
||||
} |
||||
|
||||
return $this->_indexNames = $indexNames; |
||||
} |
||||
|
||||
/** |
||||
* Generates the table name by considering table prefix. |
||||
* If [[useIndexPrefix]] is false, the table name will be returned without change. |
||||
* @param string $indexName the table name (which may contain schema prefix) |
||||
* @return string the generated table name |
||||
*/ |
||||
public function generateIndexName($indexName) |
||||
{ |
||||
if (!$this->useIndexPrefix) { |
||||
return $indexName; |
||||
} |
||||
|
||||
$db = $this->getDbConnection(); |
||||
if (preg_match("/^{$db->tablePrefix}(.*?)$/", $indexName, $matches)) { |
||||
$indexName = '{{%' . $matches[1] . '}}'; |
||||
} elseif (preg_match("/^(.*?){$db->tablePrefix}$/", $indexName, $matches)) { |
||||
$indexName = '{{' . $matches[1] . '%}}'; |
||||
} |
||||
return $indexName; |
||||
} |
||||
|
||||
/** |
||||
* Generates a class name from the specified table name. |
||||
* @param string $indexName the table name (which may contain schema prefix) |
||||
* @return string the generated class name |
||||
*/ |
||||
protected function generateClassName($indexName) |
||||
{ |
||||
if (isset($this->_classNames[$indexName])) { |
||||
return $this->_classNames[$indexName]; |
||||
} |
||||
|
||||
if (($pos = strrpos($indexName, '.')) !== false) { |
||||
$indexName = substr($indexName, $pos + 1); |
||||
} |
||||
|
||||
$db = $this->getDbConnection(); |
||||
$patterns = []; |
||||
$patterns[] = "/^{$db->tablePrefix}(.*?)$/"; |
||||
$patterns[] = "/^(.*?){$db->tablePrefix}$/"; |
||||
if (strpos($this->indexName, '*') !== false) { |
||||
$pattern = $this->indexName; |
||||
if (($pos = strrpos($pattern, '.')) !== false) { |
||||
$pattern = substr($pattern, $pos + 1); |
||||
} |
||||
$patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; |
||||
} |
||||
$className = $indexName; |
||||
foreach ($patterns as $pattern) { |
||||
if (preg_match($pattern, $indexName, $matches)) { |
||||
$className = $matches[1]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return $this->_classNames[$indexName] = Inflector::id2camel($className, '_'); |
||||
} |
||||
|
||||
/** |
||||
* @return Connection the Sphinx connection as specified by [[db]]. |
||||
*/ |
||||
protected function getDbConnection() |
||||
{ |
||||
return Yii::$app->get($this->db, false); |
||||
} |
||||
} |
@ -0,0 +1,67 @@
|
||||
<?php |
||||
/** |
||||
* This is the template for generating the model class of a specified Sphinx index. |
||||
* |
||||
* @var yii\web\View $this |
||||
* @var yii\sphinx\gii\model\Generator $generator |
||||
* @var string $indexName full table name |
||||
* @var string $className class name |
||||
* @var yii\sphinx\IndexSchema $indexSchema |
||||
* @var string[] $labels list of attribute labels (name => label) |
||||
* @var string[] $rules list of validation rules |
||||
*/ |
||||
|
||||
echo "<?php\n";
|
||||
?> |
||||
|
||||
namespace <?= $generator->ns ?>;
|
||||
|
||||
use Yii; |
||||
|
||||
/** |
||||
* This is the model class for index "<?= $indexName ?>".
|
||||
* |
||||
<?php foreach ($indexSchema->columns as $column): ?> |
||||
* @property <?= $column->isMva ? 'array' : $column->phpType ?> <?= "\${$column->name}\n" ?> |
||||
<?php endforeach; ?> |
||||
*/ |
||||
class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . "\n" ?> |
||||
{ |
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public static function indexName() |
||||
{ |
||||
return '<?= $generator->generateIndexName($indexName) ?>';
|
||||
} |
||||
<?php if ($generator->db !== 'sphinx'): ?> |
||||
|
||||
/** |
||||
* @return \yii\sphinx\Connection the database connection used by this AR class. |
||||
*/ |
||||
public static function getDb() |
||||
{ |
||||
return Yii::$app->get('<?= $generator->db ?>');
|
||||
} |
||||
<?php endif; ?> |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function rules() |
||||
{ |
||||
return [<?= "\n " . implode(",\n ", $rules) . "\n " ?>];
|
||||
} |
||||
|
||||
/** |
||||
* @inheritdoc |
||||
*/ |
||||
public function attributeLabels() |
||||
{ |
||||
return [ |
||||
<?php foreach ($labels as $name => $label): ?> |
||||
<?= "'$name' => " . $generator->generateString($label) . ",\n" ?> |
||||
<?php endforeach; ?> |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,15 @@
|
||||
<?php |
||||
/** |
||||
* @var yii\web\View $this |
||||
* @var yii\widgets\ActiveForm $form |
||||
* @var yii\sphinx\gii\model\Generator $generator |
||||
*/ |
||||
|
||||
echo $form->field($generator, 'indexName'); |
||||
echo $form->field($generator, 'modelClass'); |
||||
echo $form->field($generator, 'ns'); |
||||
echo $form->field($generator, 'baseClass'); |
||||
echo $form->field($generator, 'db'); |
||||
echo $form->field($generator, 'useIndexPrefix')->checkbox(); |
||||
echo $form->field($generator, 'enableI18N')->checkbox(); |
||||
echo $form->field($generator, 'messageCategory'); |
Loading…
Reference in new issue