XAMPP and the PHP CLI

Like a lot of people I do most of my development work on windows PC and locally run a web a server and database. Like a a lot of people I just downloaded and installed a server bundle. For the last few years I have used Apache2triad and it has generally been pretty painless, but I’ve recently been forced to abandon it. Unfortunately the development seems to have been abandoned about 2 years ago.

The reason I abandoned Apache2triad was PHP versions – I wanted to do some work with Google Base using the Zend Google Data Library which required PHP 5.1.4+.

Looking around at the various bundles I decided to go with XAMPP – a lot of people seem to use it and it seems to get pretty good feedback. I won’t say the process was entirely painless (it took a couple of goes to get everything up and running) but eventually it all seemed OK.

The fun started when I decided to do a bit of baking (generating scaffolding code for my CakePHP app from a command line interface). Nothing worked.

Looking into it I discovered more than I wanted to know about the PHP Command Line Interface (CLI) which I had happily used for years with no idea it actually existed, having assumed it was just there as part of PHP.

After a lot googleing and a lot of trial and error I got it all working again and this is how I did it.

My environment is Windows XP Pro Service Pack 2 with XAMPP installed on E:/

  1. Downloaded the latest version of PHP and replaced the entire contents of E:\xampp\php\.
  2. Added to the E:\xampp\php to my environment PATH.

At this stage PHP CLI wwas now working but there were nasty errors e.g. I was uanble to connect to MySQL from scripts. I discovered that this is because PHP CLI uses a different php.ini file to regular PHP.

To fix this I copied my php.ini from E:\xampp\apache\bin\php.ini (the default XAMPP ini file) to E:\xampp\php.

I then checked the PHP CLI again using php -v at a command prompt. There were now about 6 errors displayed, concerned with modules that could not be loaded or found. I believe that these concern modules just that aren’t meaningful in CLI mode. So I went through my new php.ini in E:\xampp\php and commented out the directives that were causing the errors until PHP CLI ran without errors.

CakePHP Calendar Helper

About six months ago I wrote a post about a Simple PHP calendar function I had written and how it was also really easy to use as a CakePHP helper.

Calendar Screenshot

I didn’t actually write the calendar specifically with Cake in mind, but I was working on a Cake site at the same time and I had a flash of inspiration. I was working on a project that needed a calendar, so I looked through all my old code but all the Calendars I had were all tied up in a terrible mess with bits of logic and SQL queries and layout all rolled into one. I looked on Google and still couldn’t find anything really easy to use – I wanted something I could just drop into place.

I suddenly realised that the way to do it was to stop trying to put any decision making into the calendar at all. The calendar only needs to display the right layout for the month and manage back and forward links. I decided to just dump the data in an array where the index corresponded to the day number (1 to 31) – the idea being that you can put anything in the array – plain text, html, javascript hooks for ajax etc.

It was in part influenced by some work I was doing using the The Yahoo! User Interface Library (YUI)
but I am very dubious about the whole notion of creating embedded calendars solely through Javascript when it could far more easily be done server side. (There is clearly a place for Javascript pop up calendars e.g. date pickers – I’m particularly fond of Marc Grabanski and Keith Woods’ version jQuery UI Datepicker)

At the time I had just moved over to using CakePHP as the main vehicle for my development work and it was clearly a great fit with the MVC setup of CakePHP – the calendar is just a shell that shows whatever you pour into it.

This is the first time I have had to go back and revisit the code, I have fixed the bugs, added some comments and set up a working example. At the moment all the logic is just sitting in the controller – but I am working on a component to tidy everything up and make it nice and portable (watch this space).

Thanks to everybody who commented or emailed me about the first version in September.

Calendar Helper


';
	
	$str .= '';
	
	$str .= '';
	
	$str .= $this->Html->link(__(‘prev’, true), array($prev_year, $prev_month));
	
	$str .= '' . ucfirst($month) . ' ' . $year . '';
	
	$str .= $this->Html->link(__(‘next’, true), array($next_year, $next_month));


	
	$str .= '';
	
	$str .= '';
	
		for($i = 0; $i < 7;$i++) {
				$str .= '' . $day_list[$i] . '';
		}
		
	$str .= '';
	
	$str .= '';
	
	$str .= '';
		
	while($day <= $days_in_month) {
		$str .= '';
			
		for($i = 0; $i < 7; $i ++) {
		
			$cell = ' ';
			
			if(isset($data[$day])) {
				$cell = $data[$day];
			}
				
			$class = '';
			
			if($i > 4) {
				$class = ' class="cell-weekend" ';
			}
			
			if($day == $today) {
				$class = ' class="cell-today" ';
			}
		
                        $first_day_in_month = strtolower($first_day_in_month);
			if(($first_day_in_month == $day_list[$i] || $day > 1) && ($day <= $days_in_month)) {
				$str .= '
' . $day . '
' . $cell . '
'; $day++; } else { $str .= ' '; } } $str .= ''; } $str .= ''; $str .= ''; return $str; } } ?>

Calendar Controller

The controller is responsible for creating the data array containing the calendar ‘events’ and then passing this on to the view from where the calendar is called.


Event->recursive = 0;
		
		$month_list = array('january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december');
		$day_list = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');
		$base_url = $this->webroot . 'events/calendar'; // NOT not used in the current helper version but used in the data array
		$view_base_url = $this->webroot. 'events';
		
		$data = null;
	
		if(!$year || !$month) {
			$year = date('Y');
			$month = date('M');
			$month_num = date('n');
			$item = null;
		}
			
		$flag = 0;
				
		for($i = 0; $i < 12; $i++) { // check the month is valid if set
			if(strtolower($month) == $month_list[$i]) {
				if(intval($year) != 0) {
					$flag = 1;
					$month_num = $i + 1;
					$month_name = $month_list[$i];
					break;
				}
			}
		}
					
		if($flag == 0) { // if no date set, then use the default values
			$year = date('Y');
			$month = date('M');
			$month_name = date('F');
			$month_num = date('m');
		}
		
		$fields = array('id', 'name', 'DAY(event_date) AS event_day');
		
		$var = $this->Event->findAll('MONTH(Event.event_date) = ' . $month_num . ' AND YEAR(Event.event_date) = ' . $year, $fields, 'Event.event_date ASC');
		
		/**
		* loop through the returned data and build an array of 'events' that is passes to the view
		* array key is the day of month 
		*
		*/
		
		foreach($var as $v) {
		
			if(isset($v[0]['event_day'])) {

				$day = $v[0]['event_day'];
				
				if(isset($data[$day])) {
					$data[$day] .= '
' . $v['Event']['name'] . ''; } else { $data[$day] = '' . $v['Event']['name'] . ''; } } } $this->set('year', $year); $this->set('month', $month); $this->set('base_url', $base_url); $this->set('data', $data); } } ?>

Event Model


 array(
			'rule' => array('minLength', 2),
			'message' => 'Name must be at least 2 characters long',
			'required' => true 
										),
		'notes' => array(
			'rule' => array('minLength', 2),
			'message' => 'Please add some notes',
			'required' => true 
										),	
	);

}
?>

Calendar View


calendar($year, $month, $data, $base_url); ?>

events SQL


CREATE TABLE `events` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) default NULL,
  `event_date` datetime default NULL,
  `notes` text,
  `created` datetime default NULL,
  `modified` datetime default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=latin1;

CSS


/* calendar CSS */
 
table.calendar {width: auto; border: 1px solid #cccccc; border-collapse: collapse; margin: 0px; padding: 0px; background-color: #ffffff;}
table.calendar th {background-color: #eeeeee; text-transform: none; color: #444444; padding: 4px; text-align: center; border: 1px solid #eeeeee;}
 
table.calendar th.cell-prev {text-align: left;}
table.calendar th.cell-next {text-align: right;}
table.calendar th.cell-header {width: 70px; border-bottom: 1px solid #cccccc;}
table.calendar td.cell-today {background-color: #e2e8f6;} /* today in the current month */
table.calendar td.cell-weekend {background-color: #F3F5EB;}
table.calendar td {border: 1px solid #cccccc;}
 
table.calendar td div.cell-number {text-align: right; font-size: 8px; color: #444444; display: block;}
table.calendar td div {display: block; font-size: 10px; text-align: left;}
table.calendar thead th {border: 1px solid #cccccc;}

To Do List

I’m feeling a little less time poor at the moment and want to do a bit of work on this. If anybody has any comments or suggestions, please let me know. At the moment my plans are as follows (in no particular order).

  • add support for internationalisation
  • put the checking date checking (and possibly data array construction into a component)
  • add built in AJAX support (haven’t decided on jQuery or Prototype yet though)
  • look at using Cake 1.2 routes as part of date checking using regular expressions (how easy would it be to combine this with the internationalisation?)

Examples & Downloads

Check out the Working Calendar Helper Example

A simple jQuery menu with persistence using cookies

Recently I’ve been making a concerted effort to learn jQuery the JavaScript framework as opposed to just using all the wonderful plugins off the shelf.

Recently I needed a bit of code to show and hide a navigation menu, but with persistence using cookies so as you move from page to page it can remember which sections to show. I was pretty confident, using bog standard JavaScript I knew I could knock it out really quickly, but The whole point of learning something new is to learn something new so I decided to do it using jQuery. I was pretty confident, last week I wrote some quite complex form validation code in a fraction of the time I could’ve done it in without using jQuery (it’s that pretty but it works well and it was my first attempt to do anything at all complex).

Getting the menu to work has been quite a struggle and I had to spend a surprising amount of time getting it to work, and judging by the posts on various blogs and groups a lot of other people have been stumped by this one too.

Anyway here is my solution, if anybody can help me simplify it further, their help would be greatly appreciated. Obviously you need to download jQuery, you will need to include the code and you will need an unordered list to act as the menu, in this example id=”#demo-menu”.

A couple of posts have been really invaluable figuring out how to do this, so credit where it’s due, thanks:

View the working example

$(document).ready(function() {

		$("#demo-menu > li > a").not(":first").find("+ ul").slideUp(1);
				
		$("#demo-menu > li > a > span").text('+'); // add an indicator to the menu items to show there is a child menu
		
		$("#demo-menu > li> a").each(function() {
			toggleMenu(this);
			checkCookie(this);
		});



		function checkCookie(id)
			{
				/*
				
						check if there is a cookie set for a sub menu 
						if there is then show the menu
				
				*/
				
				var cookieName = id.id;
				
				var c = readCookie(cookieName);
				
				if(c === 'show') {
					
					$(id).each(function() {
					
						$(this).children("span").text('-');
						$(this).find("+ ul").slideDown('fast');
						
					});
					
				}
			}

		function toggleMenu(id)
			{
				$(id).click(function() {
					/*
							toggle the +/- indicators
					*/
					togglePlusMinus(this);	
					
					/*
						toggle the menu open or closed
					*/
					$(this).find("+ ul").slideToggle("fast");
					
				});
			}
			
		function togglePlusMinus(id)
			{
			
				$(id).each(function() {
				
					if($(this).find("+ ul").is(':visible'))
						{
							$(this).children("span").text('+');
							eraseCookie(this.id);
						}
					else
						{
							$(this).children("span").text('-');
							createCookie(this.id, 'show', 365);
						}
				
				});
			}

});

// cookie functions http://www.quirksmode.org/js/cookies.html

function createCookie(name,value,days)
	{
		if (days)
		{
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name+"="+value+expires+"; path=/";
	}
function readCookie(name)
	{
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++)
		{
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	}
function eraseCookie(name)
	{
		createCookie(name,"",-1);
	}

Route Deja Vu

I just had one of those odd moments that happen when you are stuck and are trawling through Google and you come across something you have written. At the moment I have a blessed respite from the usual rubbish at work of sorting out and extending other peoples bad code, and I am writing a pretty big website CMS using CakePHP. (I haven’t touched it for about a month and I had quite forgotten how pleasant it can be to work with.)

I’ve got a bit of a routing issue – well kind of – I have a situation like this:

I have a controller called Products based on a table called products that also uses a table called product_items (products is actually a table of product categories – but this is all in the name of nice urls, product_items contains the actual products)

For example:

  • http://www.somewebsite.com/products – shows you a list of product categories e.g. ‘Pet Foods’
  • http://www.somewebsite.com/products/pet_foods – shows you all the products within the ‘Pet Foods’ category
  • http://www.somewebsite.com/products/pet_foods/chappie – shows you the detail page for chappie

This all works fine – the way I have done it is to do the following, I have set up the following route:


$Route->connect('/products/(.*)', array('controller' => 'products', 'action' => 'index'));

So anything gets pointed at the index method in the controller, my code within the index method looks something like this:


function index($product_cat_url = null, $product_item_url = null) {

		
		
	if($product_cat_url)
			{
                           // check the product cat is valid
                        }

        if($product_item_url)
			{
                           // check the product item is valid
                        }

        etc.

This lets me pass all the actions through a single method – works great – but now I want to add locations so for instance I might have a url like:

  • http://www.somewebsite.com/products/london/pet_foods – shows you all the products within the ‘Pet Foods’ category in london

Obviously I can just add more code in the index method, but then this in itself might get really unwieldy – but it has got me thinking is there a better way of doing this? I was Googling and found the following thread where I used a beforeFilter() to pass it to a different controller – but then I might just end up duplicating loads of code in two methods…