Simple stats collection with CodeIgniter hooks

Yesterday, I launched the alpha version of Project Trackr, a web based project management system I’m working on. As well as the feedback I would receive from the users themselves, I wanted some additional data. I wanted to track the users journey throughout the site and to get some quantitative data on on the usage of the application. With this information, I would know which features are popular are which features aren’t being used, and I could then try to find out if they’re not being used because they’re not useful, their implementation is poor or people just don’t know about it.

But surely I could find out this sort of stuff from something like Google analytics? Not really, the URLs are unique to each project, task, milestone, file etc. I also wanted the raw numerical data so that I could query it however I wanted.
My immediate thought was to log the current activity at the start of each function in my controllers, but this would be time consuming, and as a developer, we always want the simplest solution for the job. So I thought about how this could be automated.

Hooks

Seeing as I want this code to execute for every request, then surely a hook is the best solution. You could also put the following code inside the constructor of a super controller, which I tend to prefer over using hooks, but in this case I felt hooks where the best solution, as the code executed in them isn’t directly related to the execution of the controller, so I’d rather keep the 2 separated. You can read more about hooks in the user guide.

Enough talk, show me the code

Ok, so first up, we need to enable hooks, which are disabled by default. This is simply a case of changing a value in the configuration file. Open up system/application/config/config.php and find the line

$config['enable_hooks'] = FALSE;

and replace it with

$config['enable_hooks'] = TRUE;

Next up, we need to define our hook point. As stated in the documentation for hooks, there are 8 different hook points available. The hook point you choose will affect the results you get. If you use a pre system hook, you won’t have the necessary libraries loaded, if you use something list a post system hook, then it will never get executed if you have a redirect in your code. This was very important to me, as I use redirects after all insert operations, so I wouldn’t actually log any new data being entered if I used the posst system hook. In the end, I went for the post controller constructor hook. The code for the hook looks like the following, and should go in system/application/config/hooks.php

$hook['post_controller_constructor'] = array(
	'class'    => 'Statistics',
	'function' => 'log_activity',
	'filename' => 'Statistics.php',
	'filepath' => 'hooks'
);

You may change the items within the array if you wish, but remember to use your values for the rest of this.

Next, we need to decide what we actually want to track. In my case, I wanted the following:

  • Account, project and user ID. These allow me to query events at a user, project and account level
  • Section – The part of the app the request occurs in, this is the controller
  • Action – What the user is doing, this is the method in the controller
  • When – A timestamp of when this occured, so I can query across date rannges
  • URI – The URI string, as it may contain other useful information, such as item IDs etc

Based on this, I have the following SQL schema for my statistics table

CREATE TABLE IF NOT EXISTS `statistics` (
	`id` int(11) NOT NULL AUTO_INCREMENT,
	`account_id` int(11) NOT NULL,
	`project_id` int(11) NOT NULL,
	`user_id` int(11) NOT NULL,
	`section` varchar(32) NOT NULL,
	`action` varchar(32) NOT NULL,
	`when` int(11) NOT NULL,
	`uri` varchar(255) NOT NULL,
	PRIMARY KEY (`id`)
)

And lastly, the actual code for the hook which is pretty self explanatory

<?php if(!defined('BASEPATH')) exit('No direct script access allowed');
class Statistics {
	public function log_activity() {
		// We need an instance of CI as we will be using some CI classes
		$CI =& get_instance();

		// Start off with the session stuff we know
		$data = array();
		$data['account_id'] = $CI->session->userdata('account_id');
		$data['project_id'] = $CI->session->userdata('project_id');
		$data['user_id'] = $CI->session->userdata('user_id');

		// Next up, we want to know what page we're on, use the router class
		$data['section'] = $CI->router->class;
		$data['action'] = $CI->router->method;

		// Lastly, we need to know when this is happening
		$data['when'] = time();

		// We don't need it, but we'll log the URI just in case
		$data['uri'] = uri_string();

		// And write it to the database
		$CI->db->insert('statistics', $data);
	}
}

And that’s it! I hope this was of use to you, let me know in the comments if you have any possible improvements to this (There must be plenty, but this is just an example of something basic). And even better, share how you’re using this in your applications, how are you querying the data to help you?