目前我正在开发Yii2框架中的新网站/平台。
项目存在于多个数据库(不同的客户端/ my-sql用户帐户)之外,每个数据库都存在于具有相同结构的多个表中。我知道这是针对关系数据库的指导方针,但由于其他不同的技术原因(而不仅仅是懒惰......),无法更改数据库设置。该数据库也集成在其他程序中,所以它不可更改...... 此时不需要对数据库进行写入。
所以数据库设置如下:
db_100 --o-- tbl_A1 (tables all have the same structure)
o-- tbl_B2
o-- tbl_C3
db_200 --o-- tbl_A1
o-- tbl_B2
o-- tbl_C3
o-- tbl_D4
db_300 --o-- tbl_A1
...
数据库名称和表名称始终具有相同的前缀,未给出最大数量的数据库或表。数据库名和表名的后缀是不可预测的。 (目前有40个数据库,每个约有50个表,但仍在增长)。
由于每个表具有相同的结构,我认为从Yii2框架中使用ActiveRecord类是个好主意。 但是,ActiveRecord类使用静态方法来获取数据库连接和tableName。静态方法不能在为每个实例使用不同的表和db时创建类的实例。
/**
* Returns the database connection used by this AR class.
* By default, the "db" application component is used as the database connection.
* You may override this method if you want to use a different database connection.
* @return Connection the database connection used by this AR class.
*/
public static function getDb()
{
return Yii::$app->getDb();
}
/**
* Declares the name of the database table associated with this AR class.
* By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]
* with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is `tbl_`,
* `Customer` becomes `tbl_customer`, and `OrderItem` becomes `tbl_order_item`. You may override this method
* if the table is not named after this convention.
* @return string the table name
*/
public static function tableName()
{
return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}';
}
此时我通过使用请求中的get值来使其工作。
所以我可以在url中声明tablename和db,非常简单
http://...:8080/CustomActiveRecord/index?db=100&customTableName=A1
(简化代码)
public static function tableName() {
//get base of tablename
$customTblName = static::customTblName(); //-> Yii::$app->request->get('customTblName') ?: null;
//throw exception if null
if (is_null($customTblName )) {
throw new \yii\web\HttpException(...);
}
//return the tablename
return 'tbl_' . $customTblName;
}
我为db-connections做了类似的事情(我用所有数据库凭据填充参数数组,并在getDb中使用... request-> get(...)设置模型中的db )功能。
现在这一切都与gridview,listviews,kartik-Chartjs,...结合使用,但前提是在URL中定义了tableName和db。 这不能一次使用多个模型,这是我需要的。 (比较,统计,...)
有谁知道如何将一个ActiveRecord用于多个表/数据库? 理想情况下使用构造函数,以便我可以为每个表创建一个实例?
$model = New CustomActiveRecord(['db' => '100', 'tbl' => 'A1']);
答案 0 :(得分:0)
最近我遇到了同样的问题。
我使用了一个不太漂亮的解决方案,但是最终成功了。
首先创建一个自定义ActiveRecord
类:
use Yii;
use yii\base\InvalidArgumentException;
use yii\base\InvalidCallException;
use yii\db\ActiveRecord;
use yii\db\Connection;
class ActiveRecordCustom extends ActiveRecord
{
/**
* @var Connection[]
*/
protected static $_connections = [];
/**
* @var static[]
*/
private static $_classes = [];
private static function ensureConnection(string $db): void
{
if (!preg_match('/^[a-z0-9_]++$/i', $db)) throw new InvalidArgumentException('Argument $db is not a valid database name');
if (array_key_exists($db, self::$_connections)) return;
/* @var Connection $connection */
$connection = clone Yii::$app->get('db');
$connection->dsn = SomeHelperClass::GenerateDsn($db);
self::$_connections[$db] = $connection;
}
/**
* Creates a dynamic class (and caches it). The resulting class uses a specific DB on its connection string.
* @param string $db
* @return static
*/
public static function classForDb(string $db)
{
$calledClass = static::class;
if (!in_array(self::class, class_parents($calledClass))) throw new InvalidCallException('This function must be called from child classes only');
self::ensureConnection($db);
$classKey = "{$calledClass}_{$db}";
if (!array_key_exists($classKey, self::$_classes)) {
if (!UString::startsWith('\\', $calledClass)) $calledClass = "\\{$calledClass}";
$generatedClassName = 'dynamic_' . UString::secureRandomHexString();
$generatedClassCode = <<<HEREDOC
class {$generatedClassName} extends {$calledClass} {
public static function tableName() {
return {$calledClass}::tableName();
}
public static function getDb(): \yii\db\Connection {
return self::\$_connections['{$db}'];
}
public static function getDbName(): string {
return '{$db}';
}
}
HEREDOC;
eval($generatedClassCode);
self::$_classes[$classKey] = $generatedClassName;
}
return self::$_classes[$classKey];
}
/**
* Creates an instance of a dynamic class (and caches it). The resulting instance uses a specific DB on its connection string.
* @param string $db
* @return static
*/
public static function instanceForDb(string $db)
{
$class = self::classForDb($db);
return new $class;
}
}
UString类:
class UString {
/**
* Checks if the string $haystack starts with $needle
* @param string $haystack The string to check if starts with $needle
* @param string $needle The string used to check if $haystack starts with it
* @return bool True if $haystack starts with $needle, otherwise, false
*/
public static function startsWith($haystack, $needle) {
$length = mb_strlen($needle);
return (mb_substr($haystack, 0, $length) === $needle);
}
/**
* Generates a random HEX string with a fixed length of 128 chars. Its guaranteed to be cryptographically secure.
* @return string|bool The generated random HEX string, or false in case of failure
*/
public static function secureRandomHexString() {
$data = openssl_random_pseudo_bytes(64, $secure);
return $secure ? bin2hex($data) : false;
}
}
最后是一个示例AR:
class MyARClass extends ActiveRecordCustom {...}
现在剩下的就是使用AR类,如下所示:
对于静态方法调用:MyARClass::classForDb('some_database')::find()...
用于创建绑定到特定数据库的实例:$instanceTiedToDb = MyARClass::instanceForDb('some_database');
即使该特定代码仅适用于动态数据库连接,也很困难,它也可以轻松扩展以支持表。