Simple(or so I thought): relational tags

Hi, I have been trying to wrap my brain around this for the past few days, but for some reason I just can’t grasp it… All I am doing is inputting tags into a database. It should be easy… Anyway, this is what I am doing:

  • Accepts the input "blog,Yii"

  • Add the tags to the database IF the tag is not currently in it

  • Whether it is TRUE or FALSE add the news_ID and tags_ID to the tagsMatch table

This is my basic way of thinking, correct me if I’m wrong.

  • Grab the user tag inputs from the POST: "Yii,blog"

  • Explode the tags by the "," delimiter and clean the tags(cut out the whitespace and convert the tags to lowercase)

  • Store the tags in an Array

  • Loop through the tags

  • Check to see if the tag is in the tags database or not

  • IF TRUE - don’t add to database

  • IF FALSE - add to database

  • While in the for loop, create a newrecord for the tagsMatch that includes the news_ID and tags_ID(add to tagsMatch)

  • End

Here is my current database layout:

Since I can’t embed links, it is like the Relational Database example tutorial(the tbl_category, tbl_category_post, post) relationship

[b]TagsMatch to Tags are actually a MANY TO MANY relationship.


This is what I could have problems with:

Why can’t I do … in the foreach loop? (I get the error: “Illegal offset type in isset or empty”)

		$tagModel = new Tag(array('name'=>$tagged));


I get weird results with this code:

	        $tagModel->isNewRecord = true;

                $tagModel->primaryKey = NULL;

                $tagModel->name = $tagged;


News model:

	public function relations()


		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'comments' => array(self::HAS_MANY, 'Comments', 'news_ID'),

			'tagsMatch' => array(self::MANY_MANY, 'TagsMatch', 'news_ID'),

			'tags' => array(self::HAS_MANY, 'Tag',array('id'=>'tags_ID'),'through'=>'tagsMatch'),



Tags model:

	public function normalizeTags($attribute)


		$tags = explode(',',$this->name);

		foreach($tags as $value)


			$value = trim($value);

			$value = strtolower($value);

			$tagsArray[] = $value;


		return $tagsArray;


News Controller:

$model=new News;

        $tagModel=new Tag;

        $tagMatcher=new TagsMatch;

        //$tagsMatch=new TagsMatch;

		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);









			if($model->save()) {


				echo $tagModel->name . "=>";

				$tags = $tagModel->normalizeTags($tagModel->name);



				foreach($tags as $tagged)




						//$tagModel->isNewRecord = true;

                        //$tagModel->primaryKey = NULL;

                        //$tagModel->name = $tagged;


						$tagModel = new Tag(array('name'=>$tagged));

						echo "     =     " . $tagged . "     =     ";





    				$tagMatcher->news_ID = $model->news_ID;

    				$tagMatcher->tags_ID = $tagModel->tags_ID;


    				//$this->dbConnection->createCommand("INSERT INTO TagsMatch (news_ID, tags_ID) VALUES ({$this->id}, {$tag->id})")->execute();

						//$tagModel = Tag::model()->findByAttributes(array('name'=>$tagged));










I noticed that in the Yii blog demo and the "enhanced" Yii blog demo they have a tags field in their blog posts table. Is this a hack or is there a reason to do this? Thanks for any clarifications!!


There is something strange in your controller: you wrote


where "$model" is a object of class "Tag". So I doubt his attributes should come from a "News" form.

Another important error is with the constructor of an AR record. You can’t give it an array of attributes. So your line

$tagModel = new Tag(array('name'=>$tagged));

should be replaced by

$tagModel = new Tag();

$tagModel->name = $tagged; // or ->attributes = array("name" => $tagged);

Then, I suggest you remove some fat from your controller. You should build a function that takes an array of tags and returns an array of ids (old or just created). It will be easier to test and read your code.

A side-note on perfermance: instead of a foreach, this function could use a single query. Something like

Tag::model()->findByAttributes(array("name" => $tags));

(if the value of an attribute is an array, it gets converted into an SQL "IN").

I’d also recommend to never simply write


If there is an error, it will silently fail. You could write instead

if (!$smthing->save()) {

    Yii::log(print_r($smthing->errors, true), "error");

    throw new CHttpException(500, "...");


BTW, you don’t need to write


The default scenario, when you call "new" on an AR class, is already a "create" scenario.