Want to create a 'recently viewed items' array to store in a cookie

Hi all,

A bit of advice. I’m wanting to implement a ‘recently viewed items’ array which will store the last {n} items a user has viewed in the order of most recently viewed. This array would be stored in a cookie.

For example, the user visits the following pages in this order:

[list=1]

[*]post/id/345

[*]post/id/287

[*]post/id/123

[*]post/id/34

[*]post/id/287

[/list]

On each page load, the id is added to the array. Because post 287 was viewed twice, I would expect the recently viewed array to only have 4 entries and look something like this:

[list=1]

[*][0]=>287

[*][1]=>34

[*][2]=>123

[*][3]=>345

[/list]

So, I’m not sure how to go about it. I’ve read the CList, CStack add CQueue class descriptions, but don’t fully understand which one I am best to use.

Any help appreciated.

What you want is really a stack where you can drop duplicates, however the data structure implementation you use does not matter, an array can be made to represent a linked list, a stack or a binary tree.

I personally wouldn’t put this in a cookie but instead in your session:




//assuming your sessions are autostarted

$session = Yii::app()->session;

//assuming you settle on CStack (you need to get the $id $_GET or $_POST)

if (($key = array_search($id, $session['recentlyViewed'])) !== false) { //remove dupes

  unset($session['recentlyViewed'][$key]);

}

$stack = new CStack($session['recentlyViewed']);

$stack->push($id);

$session['recentlyViewed'] = $stack->toArray();



I wouldn’t actually even use CStack, I’d use a plain PHP array and use the array_push function on it, also you can probably stick this in a filter or something if you decide to make it reusable.

Oh by the way a stack is upside down, you can always just call array_reverse to make the latest entry at 0. Whereas if you use pop() you can pop it back off the stack in order.

Hi Luke,

Many thanks for this. This gives me a great start!

Just a question, any reason you’d use sessions rather than a cookie?

Sessions are generally more robust, with a cookie you have to be sure the client is accepting cookies (sessions are normally cookies but not always), cookies have all sorts of weird limits and constraints. You need to remember to serialize and unserialize any data structure you store in a cookie and PHP’s own manual recommends against this, so you need to jump through even more hoops (like storing the data comma separated). I’m not saying it’s a terrible idea but unless there is some reason not to use sessions, then sessions are the default way to emulate state.

Thanks again Luke.

That explanation on sessions and cookies is good enough for me. Your help has inspired me to come up with this solution storing recently viewed items in a session variable which I am very happy with. I ended up using CList for its add() and remove() methods.

ERecentlyViewedBehaviour.php




/**

 * ERecentlyViewedBehavior is a behavior for managing recently viewed model items.

 */

class ERecentlyViewedBehavior extends CBehavior

{


	/**

	 * @param integer the limit to the number of items in the recently viewed list.

	 */

	var $limit = 0; // 0 = no limit.


	/**

	 * Adds an item id to a 'recently viewed items' session object.

	 * @var string the modelClass of the item to store

	 * @param integer the id of the item to store

	 */

	protected function setRecentlyViewed($modelClass, $id)

	{

		// Create the session index

		$index = $modelClass.'_recently_viewed';


		// Check if the session index exists

		if (!isset(Yii::app()->session[$index]))

		{

			$recentlyViewed = new CList();

		}

		else

		{

			$recentlyViewed = Yii::app()->session[$index];

			// Remove the id if it is already in the list

			if ($recentlyViewed->contains($id))

			{

				$recentlyViewed->remove($id);

			}

			// If a limit is set, and the list is at (or over) the limit, remove oldest item(s)

			if ($this->limit > 0 && $recentlyViewed->count() >= $this->limit)

			{

				$count = $recentlyViewed->count() - $this->limit;

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

				{

					$recentlyViewed->removeAt(0);

				}

			}

		}


		// Add the current item id to the end of the array

		$recentlyViewed->add($id);


		// Update the session

		Yii::app()->session[$index] = $recentlyViewed;

	}


	/**

	 * Retrieves model records from a 'recently viewed items' session object.

	 * @var string the modelClass of the items to retrieve

	 */

	protected function getRecentlyViewed($modelClass)

	{

		// Create the session index

		$index = $modelClass.'_recently_viewed';


		$models = array();


		// Check if the session index exists

		if (isset(Yii::app()->session[$index]))

		{

			$recentlyViewed = Yii::app()->session[$index];


			// Check if a limit is set, and if the list is at (or over) the limit

			if ($this->limit > 0 && $recentlyViewed->count() >= $this->limit)

			{

				$count = $recentlyViewed->count() - $this->limit;

				// Remove the oldest item(s) (always an index of 0 after each removal)

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

				{

					$recentlyViewed->removeAt(0);

				}

			}


			// Convert the CList object stored in the session to an array

			$recentlyViewed = $recentlyViewed->toArray();


			// Reverse the array so the most recently added item is first

			$recentlyViewed = array_reverse($recentlyViewed);


			// Create a comma separated list for the db order property

			$recentlyViewedCommaSeparated = implode(',', $recentlyViewed);


			// Find all of the models with the array of ids recently viewed

			// and order the results in the same order as the array

			$criteria = new CDbCriteria;

			$criteria->order = "FIELD(id, $recentlyViewedCommaSeparated)"; // MySQL function

			$models = CActiveRecord::model($modelClass)->findAllByPk($recentlyViewed, $criteria);

		}


		return $models;

	}


}



In your controller, attach the behavior…




/**

 * Controller behaviors

 */

public function behaviors()

{

	return array(

		'recentlyViewed'=>array(

			'class'=>'ext.behaviors.ERecentlyViewedBehavior', // Location of the behavior class

			'limit'=>5, // Limit the number of recently viewed items stored. 0 = no limit.

		),

	);

}



Then, in your controller action(s) where you want to set or get the Recently Viewed list…

To add an item to the Recently Viewed list - use the $this->setRecentlyViewed($modelClass, $id) method, where $modelClass is a string of the model class name (i.e. ‘Post’, or get the model class name with get_class($model)), and $id is the id of the viewed model item that you want added to the list;

To retrieve the model records from the Recently Viewed list - use the $this->getRecentlyViewed($modelClass, $id) method, where $modelClass is a string of the model class name (i.e. ‘Post’, or get the model class name get_class($model)), to return an object with all of the model records in the list;




public function actionView($id)

{

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


	$modelClass = get_class($model);


	// Add this item to the recently viewed list

	$this->setRecentlyViewed($modelClass, $id);


	// Retrieve the recently viewed list

	$recentlyViewedPosts = $this->getRecentlyViewed($modelClass);


	$this->render('/post/view', array(

		'model'=>$model,		

		'recentlyViewedPosts'=>$recentlyViewedPosts,

	));

}



Finally in your View file, you can loop through the model records like any other object…




<?php

foreach ($recentlyViewedPosts as $recentlyViewedPost)

{

	echo 'ID: '.$recentlyViewedPost->id.'<br />';

	echo 'Title: '.$recentlyViewedPost->title.'<br />';

}

?>



I hope this is helpful to someone. Isn’t Yii just the best?! ::)

HELL YES THANK YOU!

Thanks @mikewalen for class code,

@Luke Jurgs, thank you for your array implementation. I have a scenario where we are sharing session in node js and Yii and CList session data was creating some problem, so just upgraded it to array implementation.

Also when if you check limit at the time of push, i think there no need to check it again when you are fetching the list so i removed the limit check code in get function.




<?php


/**

 * HRecentlyViewedBehavior is a behavior for managing recently viewed model items.

 */

class HRecentlyViewedBehaviour extends CBehavior

{


        /**

         * @param integer the limit to the number of items in the recently viewed list.

         */

        var $limit = 0; // 0 = no limit.


        /**

         * Adds an item id to a 'recently viewed items' session object.

         * @var string the modelClass of the item to store

         * @param integer the id of the item to store

         */

        protected function setRecentlyViewed($modelClass, $id)

        {

                // Create the session index

                $index = strtolower($modelClass).'_recently_viewed';


                // Check if the session index exists

                if (!isset(Yii::app()->session[$index]))

                {

                        $recentlyViewed =array();

                }

                else

                {

                        $recentlyViewed = Yii::app()->session[$index];

                        // Remove the id if it is already in the list

                        if (($key = array_search($id, $recentlyViewed)) !== false)

                        {

                            unset($recentlyViewed[$key]);

                        }

                        // If a limit is set, and the list is at (or over) the limit, remove oldest item(s)

                        $recentViewCount=count($recentlyViewed);

                        if ($this->limit > 0 && $recentViewCount>= $this->limit)

                        {

                                $count = $recentViewCount - $this->limit;

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

                                {

                                        array_shift($recentlyViewed);

                                }

                        }

                }

                

               $recentlyViewed=array_values($recentlyViewed);


                // Add the current item id to the end of the array

                $recentlyViewed[]=$id;


                // Update the session

                Yii::app()->session[$index] = $recentlyViewed;

        }


        /**

         * Retrieves model records from a 'recently viewed items' session object.

         * @var string the modelClass of the items to retrieve

         */

        protected function getRecentlyViewed($modelClass)

        {

                // Create the session index

                $index = strtolower($modelClass).'_recently_viewed';


                $models = array();


                // Check if the session index exists

                if (isset(Yii::app()->session[$index]))

                {

                        $recentlyViewed = Yii::app()->session[$index];


                        // Reverse the array so the most recently added item is first

                        $recentlyViewed = array_reverse($recentlyViewed);


                        // Create a comma separated list for the db order property

                        $recentlyViewedCommaSeparated = implode(',', $recentlyViewed);


                        // Find all of the models with the array of ids recently viewed

                        // and order the results in the same order as the array

                        $criteria = new CDbCriteria;

                        $criteria->order = "FIELD(id, $recentlyViewedCommaSeparated)"; // MySQL function

                        $models = CActiveRecord::model($modelClass)->findAllByPk($recentlyViewed, $criteria);

                }


                return $models;

        }


}

?>