Creating a simple debug() function

If you are ever like me and hate having to type print_r and echo pre blocks whenever you want to debug a variable or array, this function is for you! This is a global function that can be used to output any data that you need to debug and it comes equipped with some nifty features. For one thing, the debug() will not output any data on the website if error_reporting is turned off, which is good for instances where you forget to remove the debug() code! It will also display the file and line number where the debug() was called, and you can pass true as the second argument to do a var_dump instead of print_r. Here's the code for your enjoyment!

/**
 * Outputs/Debugs a variable and shows where it was called from
 *
 * @param mixed $var
 * @param boolean $dump
 * @param boolean $backtrace
 * @return string
 */
public function debug($var, $dump = false, $backtrace = true) {
	if (error_reporting() > 0) {
		if ($backtrace) {
			$calledFrom = debug_backtrace();
			echo '<strong>' . trim(str_replace($_SERVER['DOCUMENT_ROOT'], '', $calledFrom[0]['file'])) . '</strong> (line <strong>' . $calledFrom[0]['line'] . '</strong>)';
		}
		echo '<pre class="debug">';
		$function = ($dump) ? 'var_dump' : 'print_r';
		$function($var);
		echo '</pre>';
	}
}

Please note that this is a very basic debug function with very basic backtrace functionality. Here are a few examples on how to use the function (if for some reason you don't understand it).

debug($_SERVER);
// var_dump() instead of print_r()
debug($_SERVER, true);
// Do not display the backtrace
debug($_SERVER, false, false);

Two new scripts, Resession and Decoda

It has been a while since I released some of my scripts, but now I have two to reveal! Well actually the Resession script has been up for nearly 2 months now, but I'm finally getting around to announcing. And without further ado, I give you Resession (Session Manager) and Decoda (A BBcode style parser).

Decoda

Decoda is a lightweight class that extracts and parses a custom markup language; based on the concept of BB code. Decoda supports all the basic HTML tags and manages special features for making links and emails auto-clickable, using shorthand emails and links, and finally allowing the user to add their own code tags.

Download the latest versions of Decoda

Resession

A small lightweight script that can manage and manipulate Session data. Calls session_start() in memory so that no header errors are thrown, as well as stores the session id in the object.

Download the latest version of Resession

If you have any suggestions for either of these classes, please feel free to comment this post or send me an email!

XAMPP in Windows: Setting up vHosts

If you have ever used a local server, you would immediately realize that you can only develop one site at a time using the given root. You could however setup multiple folders for different websites within your local servers root, but the problem with that is they would all share the same root, big problem! To fix this you would have to set up virtual hosts, or vhosts for short. A vhost is a small server side trick (using Apache) that allows for multiple domains (websites) under the same root or IP address.

To get this working in XAMPP, we will need to edit our hosts file and the httpd-vhosts.conf file within your XAMPP setup. But before we begin, lets set up a quick example of what we will be trying to achieve. We will want to setup two websites within the htdocs folder, we will create 2 folders with the first one "site1" and the second "site2" (you can name these whatever you wish).

The first thing we need to do is open the Windows hosts with Notepad, which can be located at C:/WINDOWS/system32/drivers/etc. If you are using Windows Vista, you will need to right click on Notepad and click "Run as Administrator" to be able to open the hosts file. Once you have opened the hosts file, add the following 2 lines to the file and save it.

127.0.0.1		site1
127.0.0.1		site2

Secondly we will need to open the vhosts file located at C:/xampp/apache/conf/extra/httpd-vhosts.conf and add the following code. Copy and paste this code for each additional vhost you wish to setup.

# Uncomment the following
NameVirtualHost *:80 
<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot C:/xampp/htdocs/site1
	ServerName site1
	<Directory "C:/xampp/htdocs/site1">
		Options Indexes FollowSymLinks Includes ExecCGI
		AllowOverride All
		Order allow,deny
		Allow from all
	</Directory>
</VirtualHost>
<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot C:/xampp/htdocs/site2
	ServerName site2
	<Directory "C:/xampp/htdocs/site2">
		Options Indexes FollowSymLinks Includes ExecCGI
		AllowOverride All
		Order allow,deny
		Allow from all
	</Directory>
</VirtualHost>

Once you have completed both these tasks you must restart Apache. Once restarted, you can go to http://site1/ and all files and folders within "site1" should be displayed. You may do this for as many websites as you wish, just make sure to complete both tasks!

Fresh websites, Fresh updates

Well its been a while since I have posted anything useful, and I'm sorry for the lack of activity. I actually have many post ideas up on my whiteboard, its just that I have been extremely busy lately. Tomorrow I will be going on a week long cruise to Canada, so I have been preparing for that all week. Besides preparing for my trip, I have been working on two of my own websites, I will review them both below. Both of which are CakePHP applications... for your information, hah.

GameSync

http://www.gamesync.com

This is a personal project I started on over a year ago. I wanted to create a online interactive social community that is geared towards gaming and the competitive scene. It has many of the features a social community has, and on top of that has a fully robust gaming and activity system. Its something you need to try out for yourself, so please sign up! I am also looking for help (volunteered would be best) on this site, as well as searching for investors.

Starcraft 2 Armory

http://www.sc2armory.com

About 2 years ago I launched a fansite for the game Starcraft 2. Since then it has grown tremendously (just hit 10 million views!) and I recently decided to redesign it. In that process, I also decided to rebuild the whole system in CakePHP from the ground up, check it out! Oh and by the way, I am the #1 unofficial fansite for Starcraft 2... of course!

Anywho, once I get back from my vacation, I promise to put out more posts then I am now. With 2 websites out of the way, I will have so much free time. Hats off to that!

Fixing a weird cache issue in Firefox

About two weeks ago my Firefox started acting up and would not cache pages correctly. Actually, the content would cache, but any image on the actual page would load 1 by 1 and I can watch it go in sequence. This was extremely annoying, especially working in PhpMyAdmin, watching each image icon load 1 by 1 until the page was complete. This process would take nearly 5x what it usually did, so it got quite annoying real fast. I wasn't sure what caused this to happen as all my settings were normal and untouched.

So once I had enough of this, I checked Firefoxs core config (by typing about:config in the address bar). I then typed "cache" in the filter field and once it populated, I noticed the following fields were set to false (turned off).

browser.cache.disk.enable
browser.cache.memory.enable

Once I reset these to defaults (enabling it to true), my weird image cache problem vanished. I am not entirely sure if messing with these settings are ok, so if anyone has any input on the subject it would be appreciated. But besides that, everything seems to be running smoothly, and I thought it would be a good idea to let others know in case they run into it as well.

Caching each query individually

So lately I have been delving into the caching capabilities of CakePHP. Most, if not all of its capabilities work wonderfully; although I personally can't get into $cacheAction (within the controllers). The $cacheAction property only works for static and non-user generated pages, in other terms, any content that changes depending on a logged in user wont work correctly with $cacheAction (unless you want thousands and thousands of cache files). So I stopped using $cacheAction all together in my latest application, and instead built a method that caches individual queries, instead of the whole page. All the modifications have been applied to the models find() method. To use this, place the following code within your app/app_model.php.

/**
 * Wrapper find to cache sql queries
 * @param array $conditions
 * @param array $fields
 * @param string $order
 * @param string $recursive
 * @return array
 */
public function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
	if (Configure::read('Cache.disable') === false && Configure::read('Cache.check') === true && isset($fields['cache']) && $fields['cache'] !== false) {
		$key = $fields['cache'];
		$expires = '+1 hour';
		if (is_array($fields['cache'])) {
			$key = $fields['cache'][0];
			if (isset($fields['cache'][1])) {
				$expires = $fields['cache'][1];
			}
		}
		// Set cache settings
		Cache::config('sql_cache', array(
			'prefix' 	=> strtolower($this->name) .'-',
			'duration'	=> $expires
		));
		// Load from cache
		$results = Cache::read($key, 'sql_cache');
		if (!is_array($results)) {
			$results = parent::find($conditions, $fields, $order, $recursive);
			Cache::write($key, $results, 'sql_cache');
		}
		return $results;
	}
	// Not cacheing
	return parent::find($conditions, $fields, $order, $recursive);
}

In the next step, you would create a folder called sql within your tmp/cache/ and chmod the permissions to 777. Once you have created the folder, open up your app/config/core.php file and place the following code at the bottom (near the default cache settings).

Cache::config('sql_cache', array(
    'engine'		=> 'File',
    'path'		=> CACHE .'sql'. DS,
    'serialize'	=> true,
));

By default, caching will not work on your applications queries, you would need to set an additional "cache" option within your find(). Each SQL cache should have its own unique identifier so that it does not conflict with other queries. Also by default, queries will be cached for one hour and will be saved as a serialized array. The following examples explain how the cache option works.

// Cache query to /tmp/cache/sql/model-test_sql_query
$results = $this->Model->find('all', array(
	'cache' => 'test_sql_query'
));
// Cache query to /tmp/cache/sql/model-another_query that expires in 24 hours
$results = $this->Model->find('all', array(
	'cache' => array('another_query', '+24 hours')
));

What if I have a query that's used multiple times but each has its own limit (custom method), but uses the same cache slug? Simply give the cache slug a dynamic name like so:

// Cache query to /tmp/cache/sql/model-dynamic_query-15 
$results = $this->Model->find('all', array(
	'limit' => $limit, // 20, 30, etc
	'cache' => 'dynamic_query-'. $limit
));

I personally have found an increase in load times up to 150-200% faster using this method. This should only be applied to queries that are used on landing pages, and queries that do not change according to which user is logged in. Have fun.

Easily resetting your stylesheets

I always hear talk about reset CSS, and how every website should encompass them. I mean it is everywhere. I honestly never found the use or reasoning to include an additional stylesheet, just to render elements as "dull". I simply use the code below to reset only elements I need to worry about, or ones that are used as the majority.

html, body, div, img, form, fieldset, ul, ol, li, h1, h2, h3 {
    border: none;
    padding: 0;
    margin: 0;
}

I personally like the way some elements are styled by default, mainly the table and its child elements. I use to use the global declaration of * to reset ALL elements in the page, but that caused weird and unexpected styling issues that I did not like. But to simply put it, its all a matter of personal preference, and to me you should only reset elements that are actually used (this also saves a few bytes on the filesize, boosh!).

CSS/JS/Asset Compression in CakePHP

This code was developed for CakePHP 1.2 and will no longer work on the most recent versions. You should be using an asset handler plugin in 1.3+.

The other day I spent way too many hours trying to figure out CakePHPs built in CSS and JS compression system. To say the least, its not really built in, you have to configure and do a lot of it yourself. I did much searching and found tutorials that didn't work, or using custom built helpers that were of no use, and in the end I wrote my own script (based off webroot/css.php written by gwoo). My script does the following:

  • Compresses both CSS and JS, using one file
  • Caches the assets, and recaches if a change is made to the original
  • Comes prebuilt with CSS compression
  • Must download and install JSMin
  • Extremely easy to install and configure

To use my asset compression script, create a file called assets.php within webroot, and then copy and paste the code found at the end of this post. Once you have done this, download and place the jsmin.php file within /vendors/ or /app/vendors/ (the JSMin file should be named jsmin.php to work). The next step is to create a folder called assets within your /app/tmp/cache/ directory, and chmod the permissions to 777. The final step is to uncomment the filtering in core.php and set the path to assets.php.

Configure::write('Asset.filter.css', 'assets.php');
Configure::write('Asset.filter.js', 'assets.php');

Now to take it for a test spin! Once you have uncommented the configuration, direct your browser to the actual JS and CSS paths to see if it works (http://www.domain.com/ccss/style.css), and that's it! Hope this has been helpful and easy, like it should have been!

<?php 
// No cake installation
if (!defined('CAKE_CORE_INCLUDE_PATH')) { 
	header('HTTP/1.1 404 Not Found');
	exit('File Not Found'); 
} 
// Get asset type
$ext = trim(strrchr($url, '.'), '.');
$assetType = ($ext === 'css') ? 'css' : 'js';
// Wrong file
if (preg_match('|\.\.|', $url) || !preg_match('|^c'. $assetType .'/(.+)$|i', $url, $regs)) {
	die('Wrong File Name');
}
$cachePath = CACHE .'assets'. DS . str_replace(array('/','\\'), '-', $regs[1]);
$fileName = $assetType .'/'. $regs[1];
if ($assetType == 'css') {
	$filePath = CSS . $regs[1];
	$fileType = 'text/css';
} else {
	$filePath = JS . $regs[1];
	$fileType = 'text/javascript';
}
if (!file_exists($filePath)) {
	die('Asset Not Found');
}
/**
 * Compress the asset
 * @param string $path
 * @param string $name
 * @return string
 */
public function compress($path, $name, $type) {
	$input = file_get_contents($path);
	if ($type == 'css') {
		$stylesheet = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $input);
		$stylesheet = str_replace(array("\r\n", "\r", "\n", "\t", '/\s\s+/', '  ', '   '), '', $stylesheet);
		$stylesheet = str_replace(array(' {', '{ '), '{', $stylesheet);
		$stylesheet = str_replace(array(' }', '} '), '}', $stylesheet);
		$output = $stylesheet;
	} else {
		App::import('Vendor', 'jsmin');
		$output = JSMin::minify($input);
	}
	$ratio = 100 - (round(strlen($output) / strlen($input), 3) * 100);
	$output = "/* File: $name, Ratio: $ratio% */\n". $output;
	return $output;
}	
/**
 * Cache the asset
 * @param string $path
 * @param string $content
 * @return string
 */
public function cacheAsset($path, $content) {
	if (!is_dir(dirname($path))) {
		mkdir(dirname($path));
		chmod($path, 0777);
	}
	if (!class_exists('File')) {
		uses('file');
	}
	$cached = new File($path);
	return $cached->write($content);
}
// Do compression and cacheing
if (file_exists($cachePath)) {
	$templateModified = filemtime($filePath);
	$cachedModified = filemtime($cachePath);
	if ($templateModified > $cachedModified) {
		$output = compress($filePath, $fileName, $assetType);
		cacheAsset($cachePath, $output);
	} else {
		$output = file_get_contents($cachePath);
	}
} else {
	$output = compress($filePath, $fileName, $assetType);
	cacheAsset($cachePath, $output);
	$templateModified = time();
}
header("Date: ". date("D, j M Y G:i:s", $templateModified) ." GMT");
header("Content-Type: ". $fileType);
header("Expires: ". gmdate("D, j M Y H:i:s", time() + DAY) ." GMT");
header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1
header("Pragma: cache_asset");        // HTTP/1.0
echo $output; ?>

Refreshing the Auths session

If you have developed with the AuthComponent at all, you would know by now that the auth session does not refresh when ever a user updates its information (such a drawback). This is extremely useful in many situations, especially when a user updates his profile, and you need to echo the new content in the views. I have written the following method, which should be placed in your AppController. It can refresh the whole session or just a single key in the session.

/**
 * Refreshes the Auth session
 * @param string $field
 * @param string $value
 * @return void 
 */
public function _refreshAuth($field = '', $value = '') {
	if (!empty($field) && !empty($value)) { 
		$this->Session->write($this->Auth->sessionKey .'.'. $field, $value);
	} else {
		if (isset($this->User)) {
			$this->Auth->login($this->User->read(false, $this->Auth->user('id')));
		} else {
			$this->Auth->login(ClassRegistry::init('User')->findById($this->Auth->user('id')));
		}
	}
}

To refresh the whole session, you would call this method in an action while passing no arguments. If you would like to refresh a users email, you would pass email as the first argument, and the new email as the second. This method assumes you are using the SessionComponent and a User model.

// Refresh whole session
if ($this->User->save($this->data)) {
    $this->_refreshAuth();
    $this->Session->setFlash('Your information has been updated!');
}
// Refresh single key
$this->_refreshAuth('email', $this->data['User']['email']);

This should work fine for the time being, or at least until the Cake Team adds a refresh method to the AuthComponent. Cheers.

Proxy Search - Doing a search while using named parameters

While working on a project of mine, I wanted to be able to do a search, but have the post data be retained as named parameters (basically like a $_GET string). My first attempt was setting the form method to get, and trying to convert the $_GET by doing some mod_rewrite magic. This didn't work out at all, mainly because I couldn't get it to work for multiple named parameters. The only alternative I found was suggested by someone in the Google groups; they suggested posting a form to another action to deal with the logic, and finally redirect applying named parameters. This works perfectly, albeit adding an extra step in the process.

The process I created is something id like to call a Proxy Search. The following code should be placed in your app_controller.php file, so that it can be used as a global search by all controllers. You should not overwrite the proxy() action, unless you want specific logic in a certain controller.

/**
 * Sorts the named params for a posted page
 * @param string $model
 * @access public
 */
public function proxy($model) {
	$data = array_map('urlencode', $this->data[Inflector::camelize($model)]);
	$referer = explode('/', trim($this->referer(), '/'));
	$router = array_merge(array('controller' => $referer[0], 'action' => $referer[1]), $data);
	$this->redirect($router);
}	
/**
 * Applies the named params to the controller data
 * @return array
 */
public function _proxyGather() {
	return array_map('urldecode', $this->params['named']);
}

I will quickly try to explain how the form works. For example, if we are in the Users controller and want to do a search using the search() action, the forms url should point to /users/proxy/user instead of /users/search. Now shouldn't it be /users/proxy you ask? Well no, we need to know what model should be used in $data, so we need to pass an argument for the model that is used for the form (during $form->create()).

// UsersController::search()
echo $form->create('User', array('url' => array('controller' => 'users', 'action' => 'proxy', 'user')));
// If searching through books
echo $form->create('Book', array('url' => array('controller' => 'books', 'action' => 'proxy', 'book')));

Now back in the search() action, we need to grab the named params and pass them to the fields in the form. We do this by using the _proxyGather() method. No model name needs to be passed to _proxyGather(), simply because the data is pulled from the controller.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}

You can also have this interact with pagination quite easily. All you need to do is add some conditional logic in the controller, and pass the data to paginator.

/**
 * Search players
 * @access public 
 */
public function search() {
    $this->data['User'] = $this->_proxyGather();
    if (!empty($this->data['User']['username'])) {
        $this->paginate['User']['conditions']['User.username LIKE'] = '%'. $this->data['User']['username'] .'%';
    }
    if (!empty($this->data['User']['firstName'])) {
        $this->paginate['User']['conditions']['User.firstName LIKE'] = '%'. $this->data['User']['firstName'] .'%';
    }
    $this->pageTitle = 'Search Users';
    $this->set('results', $this->paginate('User'));
}
// In the views using $paginator
$paginator->options(array('url' => $this->passedArgs));

I hope this has been helpful to someone who was looking to do a search system using named params, I know I had fun building it! I was thinking of turning this into a component, but have not figured out a way to do so yet, enjoy anyways!