My solution
PGSQL (schema != ‘public’ for my case…) !important
--
-- Name: dynamic_field; Type: TABLE;
--
CREATE TABLE dynamic_field (
id_model character(32) NOT NULL,
name character varying(45) NOT NULL,
value text
);
--
-- Name: dynamic_model_field; Type: TABLE;
--
CREATE TABLE dynamic_model_field (
model character varying(100) NOT NULL, ## Model class name ##
name character varying(45) NOT NULL, ## attribute name ##
structure text
);
*structure (to be serialized)
Array
(
## how it's done ##
[column] => Array
(
[name] => certificazione_sem
[rawName] => `certificazione_sem`
[allowNull] => 1
[dbType] => varchar
[type] => string
[defaultValue] =>
[size] =>
[precision] =>
[scale] =>
[isPrimaryKey] =>
[autoIncrement] =>
[comment] =>
)
## how to view it ##
[htmlOptions] => Array
(
[span] => col-lg-4
[type] => radio
[listData] => Array
(
[data] => Array
(
[] =>
[1] => Certificato
[2] => Non Certificato
)
)
)
)
DBAdapter (support for dynamic schemes)
/**
* Created by PhpStorm.
* User: Davide Vallicella
* Date: 11/11/14
* Time: 15.43
*/
class DBAdapter implements Singleton
{
private static $instance = NULL;
private static $dbuser = NULL;
private $schema = 'public';
.
. //custom code
.
/**
* @return string $schema
*/
function getSchema()
{
return $this->schema;
}
/**
* @return DBAdapter.
*/
static function getInstance()
{
if (self::$instance === NULL) {
self::$instance = new DBAdapter();
}
return self::$instance;
}
.
. //custom code
.
}
interface Singleton {
public static function getInstance();
}
PgSchemaConnection (support for dynamic schemes)
/**
* Created by PhpStorm.
* User: Davide Vallicella
* Date: 21/11/14
* Time: 10.59
*/
class PgSchemaConnection extends CDbConnection
{
protected function initConnection($pdo)
{
parent::initConnection($pdo);
$pdo->exec("SET search_path TO " . DBAdapter::getInstance()->getSchema() . ", public");
}
}
DynamicFieldCActiveRecord
<?php
/**
* Created by PhpStorm.
* User: Davide Vallicella
* Date: 12/10/2015
* Time: 10:07
*/
abstract class DynamicFieldCActiveRecord extends CActiveRecord
{
private static $_md = array(); // class name => meta data
/**
* Constructor.
*
* @param string $scenario scenario name. See {@link CModel::scenario} for more details about this parameter.
*
*/
public function __construct($scenario = 'insert')
{
if (DBAdapter::getInstance()->getSchema() !== 'public') { // this code is a support for dynamic schemes
$this->attachBehavior('dynamicFieldBehavior', 'ext.dynamicfield.behaviors.DynamicFieldBehavior');
}
parent::__construct($scenario);
}
public function behaviors()
{
if (DBAdapter::getInstance()->getSchema() === 'public') { // this code is a support for dynamic schemes
return parent::behaviors();
}
return array_merge(
parent::behaviors(),
[
'dynamicFieldBehavior' => ['class' => 'ext.dynamicfield.behaviors.DynamicFieldBehavior']
]
);
}
public function onAfterGetMetaData($event)
{
$this->raiseEvent('onAfterGetMetaData', $event);
}
/**
* Returns the meta-data for this AR
*
* @return DynamicCActiveRecordMetaData the meta for this AR class.
*/
public function getMetaData()
{
$className = get_class($this);
if (!array_key_exists($className, self::$_md)) {
self::$_md[$className] = NULL; // preventing recursive invokes of {@link getMetaData()} via {@link __get()}
self::$_md[$className] = new DynamicCActiveRecordMetaData($this);
$this->onAfterGetMetaData(new CEvent($this, ['data' => &self::$_md[$className]]));
}
return self::$_md[$className];
}
/**
* Returns the dynamic structure meta-data for this AR
*
* @return DynamicCActiveRecordMetaData the meta for this AR class.
*/
public function getDynamicFields()
{
$className = get_class($this);
return self::$_md[$className]->dynamicFields;
}
}
/**
* DynamicCActiveRecordMetaData represents the dynamic meta-data for an Active Record class.
*
* @author Davide Vallicella <vallicella.davide@gmail.com>
* @version 1.0
*/
class DynamicCActiveRecordMetaData extends CActiveRecordMetaData
{
public $dynamicFields = [];
}
DynamicFieldBehavior
<?php
/**
* DynamicFieldBehavior
* Automatically add dynamic user fields
*
* Author: Davide vallicella <vallicella.davide@gmail.com>
* Version: 1.1
*/
class DynamicFieldBehavior extends CActiveRecordBehavior
{
public function events()
{
return array_merge(
parent::events(),
[
'onAfterGetMetaData' => 'getMetaData',
]
);
}
public function afterFind($event)
{
if ($this->owner->dynamicFields) {
$schema = DBAdapter::getInstance()->getSchema(); // this code is a support for dynamic schemes
$commandBuilder = $this->owner->getCommandBuilder();
$criteria = new CDbCriteria;
$criteria->compare('id_model', $this->owner->primaryKey);
$dmd = $commandBuilder->createFindCommand("{$schema}.dynamic_field", $criteria)->queryAll();
foreach ($dmd as $md) {
$this->owner->$md['name'] = $md['value'];
}
}
}
public function afterSave($event)
{
if ($this->owner->dynamicFields) {
$schema = DBAdapter::getInstance()->getSchema(); // this code is a support for dynamic schemes
$commandBuilder = $this->owner->getCommandBuilder();
$criteria = new CDbCriteria;
$criteria->compare('id_model', $this->owner->primaryKey);
// delete current links to related model
$commandBuilder->createDeleteCommand("{$schema}.dynamic_field", $criteria)->execute();
foreach (array_keys($this->owner->dynamicFields) as $k) {
// otherwise make and execute insert command
$commandBuilder->createInsertCommand(
"{$schema}.dynamic_field",
[
'id_model' => $this->owner->primaryKey,
'name' => $k,
'value' => $this->owner->$k
]
)->execute();
}
}
}
/**
* Responds to {@link DynamicFieldCActiveRecord::getMetaData}.
* Override this method and make it public if you want to handle the corresponding event
* of the {@link CBehavior::owner owner}.
*
* @param CEvent $event event parameter
*/
public function getMetaData($event)
{
$className = get_class($this->owner);
/** @var $data DynamicCActiveRecordMetaData */
$data = $event->params['data'];
$schema = DBAdapter::getInstance()->getSchema(); // this code is a support for dynamic schemes
if (array_key_exists('id', $data->columns)) {
$commandBuilder = $this->owner->getCommandBuilder();
$criteria = new CDbCriteria;
$criteria->compare('model', $className);
$dmd = $commandBuilder->createFindCommand("{$schema}.dynamic_model_field", $criteria)->queryAll();
foreach ($dmd as $md) {
$name = $md['name'];
if (array_key_exists($name, $data->columns)) {
continue;
}
$structure = unserialize($md['structure']);
$column = new CPgsqlColumnSchema();
foreach ($structure['column'] as $k => $v) {
$column->$k = $v;
}
$data->columns[$name] = $column;
if ($column->defaultValue !== NULL) {
$data->attributeDefaults[$name] = $column->defaultValue;
}
$data->dynamicFields[$name] = $structure['htmlOptions'];
}
}
}
}