please test my AR Enhancement: automatically sync MANY_MANY table when calling save()

solved. one of my tables was myisam.

I think first version (+update part) is the best version. Is there any alternative for this script right now ( after about 2 years )

Check Thyseus’ extensions, he continued this thought process into a packaged extension long ago.

Hey Rangel Reale:

When use ‘$this->owner’. It occurs the following error: Property “UserGroupsUser.owner” is not defined

Please help me

Cheers

http://code.google.com/p/yii-user-management/source/browse/trunk/user/components/CAdvancedArBehavior.php

In line 159 the id of the realted object is retrieved if a object is passed to store as related:


$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

I think it must be:


$foreignobject = $foreignobject->{$foreignobject->tableSchema->primaryKey};

Example:

Category.id

PostCategories.category_id

PostCategories.post_id

Post.id

$Post->categories = array(Category::model()->findByPk(1));

$foreignobject is an object of Category, its id is stored in $foreignobject->id but the value of $relation[‘m2mForeignField’] is category_id.

Ihe downloads at http://www.yiiframework.com/extension/cadvancedarbehavior/ are outdated (v0.1 and v0.2)

As people write above,

the latest version is on google code and you should apply mrbig’s patch

thyseus, please update your extension on yii site and on google code!

Hey people,

good to know that people still uses this extension.

I just packaged a new version.

Thanks to dhampik to drop me a notice that there is still demand for this extension.

changelog:

  • added $ignoreRelations to ignore specified relations. The Behavior

will take all found many2many relations by default. Specify exceptions in

this array.

  • fixes all the bugs and glitches found in the discussion

please TEST and tell me if i forgot something ? :)

thyseus,

I just tested it and I get an error when I try to save model with the current version of extension.

The exception is on line


$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

It seems that the following happens:

3 tables {{news <id, title, text>}}, {{news_region <id, news_id, region_id>}} and {{region <id, name>}}.

Your script tries to get region.region_id property, instead of region.id property and that causes an error.

This problem could be solved with mrbig’s patch. See the discussion above.

Thanks for the useful extension.

A. With Postgresql as database, I had to change the following lines.




142c142

<                                               $info['m2mThisField'] = $this->owner->tableSchema->PrimaryKey;

---

>                                               $info['m2mThisField'] = $this->owner->tableSchema->primaryKey;






193c193

<               return sprintf("delete ignore from %s where %s = '%s'",

---

>               return sprintf("delete from %s where %s = '%s'",



B. To recap procedure to use this extension (collected from various places)

Use Case : A medicine contains many constituents, A constituent can be part of many medicines.

  1. Download into protected/extensions http://www.yiiframework.com/extension/cadvancedarbehavior/files/CAdvancedArBehavior.php.bz2

  2. Extract




bunzip2 CAdvancedArBehavior.php.bz2



  1. Import in config/main.php

        

'import'=>array(

                'application.models.*',

                'application.components.*',

+               'application.extensions.CAdvancedArBehavior',

        ),



  1. To define many to many relation between medicine and contents

In relations() method of models/Constituent.php




   return array(

+ 'medicines' => array(self::MANY_MANY,'Medicine','masters.medicine_contents(constituent_id,medicine_id) '),

  );



In relations() method of models/Medicine.php




   return array(

+'constituents' => array(self::MANY_MANY,'Constituent','masters.medicine_contents(medicine_id,constituent_id) '),

  );



5. To make CAdvancedArBehavior available, add the following in models/Medicine.php




+       public function behaviors(){

+          return array( 'CAdvancedArBehavior' => array(

+            'class' => 'application.extensions.CAdvancedArBehavior'));

+          }



  1. In actionCreate() and actionUpdate() methods of controllers/MedicineController.php
  • Assign constituents from POST before save() is called.

  • Based on the relations defined for medicine (above), CAdvancedArBehavior makes corresponding entries in medicine_contents table




+$medicine->constituents=$_POST["Medicine"]["constituents"];

$medicine->save();

$this->redirect(array('view','id'=>$medicine->medicine_id));



  1. UI to select 5 constituents for each medicine using a Drop Down list (To Do : option to select 0 or more, using javascript)

In views/medicine/_form.php




<div class="row">

<?php

$criteria=new CDbCriteria(array('order'=>'constituent_name',));

$constituentArs=Constituent::model()->findAll($criteria);


for($i=0; $i<5; $i++)

  {

  ?>

  <div class="row"> 

    <?php echo CHtml::activeDropDownList($medicine, "constituents[$i]", CHtml::listData($constituentArs, 'constituent_id', 'constituent_name'),array('prompt'=>'--Select--'));?>

<?php //echo $form->textField($constituent,'quantity',array('size'=>4,'maxlength'=>4));?>

  </div> 

  <?php

  }

?>

</div>



The extension is very useful but, I think you must apply mrbig’s patch. Because the problem when saving. (If you don’t specify any Many Many relation to before save, there will be problem).

Hi all,

nice extension.

I got it to work quite easily, the only problem I had to fix by my own is about the kind of db connection it uses. In my project I have to connect to different databases according to the specific activerecord class I’m working with.

To do this I prepared some customized activerecord master classes (overriding the getDbConnection() method) from which I let my classes inherit accordingly.

However, the main point here is that I cannot always use Yii::app()->db connection, that is exactly the connection used in CAdvancedArBehavior extension.

Thus I simply replaced this:




        public function execute($query) {

		return Yii::app()->db->createCommand($query)->execute();

	}



with this:




        public function execute($query) {

		return $this->owner->dbConnection->createCommand($query)->execute();

	}



This should work in any case, either your class extends the standard CActiveRecord base class or a modified version of it (as in my case).

And of course I had to use mrbig’s patch as well to get everything work:

hope this can help someone else.

Hi everyone,

I noticed that this very usefull extension has absolutely no protection againts mysql injections.

I used this extension in conjuction with CHtml::checkBoxList(). One can easily inspect the page, and edit the value of a checkbox adding his sql code along with the value.

To stay safe, please update the code as follows:




	public function makeManyManyInsertCommand($relation, $value) {

		return sprintf("insert into %s (%s, %s) values (%s, %s)",

				$relation['m2mTable'],

				$relation['m2mThisField'],

				$relation['m2mForeignField'],

				Yii::app()->db->quoteValue(

                        $this->owner->{$this->owner->tableSchema->primaryKey}

                ),

				Yii::app()->db->quoteValue($value)

        );

	}


	public function makeManyManyDeleteCommand($relation) {

		return sprintf("delete ignore from %s where %s = %s",

                    $relation['m2mTable'],

                    $relation['m2mThisField'],

                    Yii::app()->db->quoteValue(

                        $this->owner->{$this->owner->tableSchema->primaryKey}

                    )

				);

	}



I recommend copy pasting this code over the functions you have because I edited the sql too, not only added the quoteValue() function.

Please don’t make other mistakes like this.

Thanks

Hi,

I used to have problems when saving an array of Model objects until I found mrbig’s patch and applied it. Just a heads up since the latest version (version 0.3) still doesn’t contain the patch.

@shark0der:

I believe that the enhancement assumes valid input - so there is no need to sanitize at the “behavior” level. It is up to the user to ensure the integrity of the data that will passed to and used by the behaviors. Let’s take the way you used the extension as an example. You use it in conjunction with a checkbox list - I assume you are not checking if the ids posted indeed correspond to actual/existing record ids. Otherwise, you won’t be needing to apply data sanitation in the behavior.

You forgot the golden rule of web development: NEVER TRUST USER INPUT!.

It’s not about checking if the ids exist in the database, it’s about peeps that insert malicious code instead.

I’ve a problem using this ext.

I’ve 2 Entity ParkingSlot and Street and the Relation ParkingSlot2Street.

I’ve defined 2 Models with MANY_MANY relation and i followerd the examples, and I did care about even the order of the fields in the relations definition.

Howerver If I try to add streets to ParkingSlot (inside the Controller) with:




$parking_slot = $this->loadModel($id);

$parking_slot->street = 1;

// or

$parking_slot->street = Street::model()->findByPk(1);

// or

$parking_slot->street = array(1);



I get the following error:




Property "ParkinSlot.Array" is not defined. (......\framework\base\CComponent.php:131) ....



I also add a $street property into the ParkingSlot Model but still the same!!

Any Hints??

Thanks guys for the work. Looks amazing.

For those of you like me just getting here, this behavior is part of a bigger project yii user management.

The initial repository was an svn one in Google Code. However, the latest one got moved to github.

You may find it here:

CAdvancedArBehavior.php

I’m having some problems with this extensions.

I think it’s all well defined. When i save new objects, the relation registers are created and everything seems ok.

The problem is when i use Table::model()->findByPk($pk) and i try to modify any field of the retrieved register in the main table.

When I save it, I get an error here in CAdvancedArBehavior.writeRelation() at the line:


$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

It says Property "GCategorias.3" is not defined. It seems that the retrieved object by the MANY_MANY relation is not the same as the expected object.

If I print the object before save, this is what i get:


Array

(

    [0] => GCategorias Object

        (

            [_new:CActiveRecord:private] => 

            [_attributes:CActiveRecord:private] => Array

                (

                    [id_g_categoria] => 3

                    [descripcion] => voluntariado

                    [grupo] => 1

                )


            [_related:CActiveRecord:private] => Array

                (

                )

........

)

I have solved this bug changing the write relation method like this:


protected function writeRelation($relation) 

	{

		$key = $relation['key'];


		// Only an object or primary key id is given

		if(!is_array($this->owner->$key) && $this->owner->$key != array()) 		

			$this->owner->$key = array($this->owner->$key);


		// An array of objects is given

		foreach((array)$this->owner->$key as $foreignobject)

		{


			if(!is_numeric($foreignobject) && is_object($foreignobject)){

                            

                            if (is_numeric($foreignobject->$relation['m2mForeignField'])){

                                $foreignobject=$foreignobject->$relation['m2mForeignField'];

                            } else {

				$foreignobject = $foreignobject->{$foreignobject->$relation['m2mForeignField']};

                            }

                        }

			$this->execute(

					$this->makeManyManyInsertCommand($relation, $foreignobject));

		}

	}

Is it a bug or am I doing something wrong?

Do this extension supersede advancedrelationsbehavior?

what about the controller. What should be within the actionCreate?