Dynamic model

hi guys, I’ve been researching, reading and trying to get the best approach to do it. I need to create the classic model “product” but with a lot of “dynamic” fields so here is the big problem. Checking the core classes I found that yii is not prepared to fulfill my needs. It would be great create one model including the “dynamic fields” (of course, a custom CActiveRecord would be responsible for save, edit, delete, create).

I’m racking my brain to do it but I need some ideas, tips or whatever usefull. I think what I need is the EAV approach. What should be better override __set and __get method or maybe create a complex getmetadata?.

We have to deal with differents data types, validation, relations, son on.

I dont know…I would like to hear what you have in mind. Thanks in advance.

try looking at this :

Yii-EAV . may be a dynamic model(not too difficult. i will call it dynaForm/dynaModel ) receive a array as its underlying data , you can dynamic addValidateRule , then use FormBuilder to generate a form .

about the dynaForm now there is not such a extension but you can refer to yiimongodbsuite the softDocument will give you some idea (initSoftAttributes method …) EMongoSoftDocument.

then you can use your dynaForm and a formConfig array to render a form usingFormBuilder

i think accomplish all these task will wast you at least 4 day :P (it 's a little difficult thing for me )

hope you find a solution and share your final methods here . gook luck! :D

Thank you yiqing. I tried Yii-Eav ext but it isn’t what I’m looking for. I find a possible solution but it’s too complicated and I really wanna give up :( . I would like to hear some other approach before doing that.

I know this issue was previously discussed but remains unresolved.

Best.

why not talk about your opinion out. may be we can give you some suggestion :lol:

This is the solution I came up with - which I applied to extending a Form Model (this could get applied to an activerecord as well). The assumption is that there will be some methods implemented to modify the contents of the “_dynamicFields” property. For example’s sake I just hard coded the value in there…





class DynamicFormModel extends CFormModel {

	

	private $_dynamicData=array();

	private $_dynamicFields = array(

		 'firstname' => 1,

		 'lastname' => 1

	);

	

	public function rules() {

		return array(

			array('firstname, lastname', 'safe')

		);

	}

	

	public function attributeNames() {

		return array_merge(

			parent::attributeNames(),

			array_keys($this->_dynamicFields)

		);

	}


	/**

	 * Returns the value for a dynamic attribute, if not, falls back to parent

	 * method

	 * 

	 * @param type $name

	 * @return type 

	 */

	public function __get($name) {

		if (!empty($this->_dynamicFields[$name])) {

			if (!empty($this->_dynamicData[$name])) {

				return $this->_dynamicData[$name];

			} else {

				return null;

			}

			

		} else {

			return parent::__get($name);

		}

	}

	

	/**

	 * Overrides the setter to store dynamic data.

	 * 

	 * @param type $name

	 * @param type $val 

	 */

	public function __set($name, $val) {

		if (!empty($this->_dynamicFields[$name])) {

			$this->_dynamicData[$name] = $val;

		} else {

			parent::__set($name, $value);

		}

	}

	

}




Thank you EugeneA. it is very useful for me.

Very helpful, thanks!

Thanks for your replies. My choice was MongoDB.

See ya guys.

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'];

            }

        }

    }

}