Tips and tricks for LAMP (Linux Apache MySQL PHP) developers.

When I cant find a suitable solution to a problem and have to work it out for myself I'll post the result here so hopefully others will find it useful. (All code is offered in good faith. It may not be the best solution, just a solution).

Thursday, 20 November 2014

SimpleXML and mixed, nested namespaces

I work with several third party marketplace APIs, one of which is Amazon's MWS API.
When processing their GetMatchingProductForId response I discovered that they sometimes namespace the XML nodes (with ns2).

PHP's SimpleXML doesn't allow access to namespaced nodes via the usual interface of $simpleXml->node but I found you can specify a namespace to the children() method like so:

$simpleXml
    ->children('ns2', true)
    ->namespacedNode

That works great but then I hit a problem. As I mentioned, Amazon namespace some of the nodes but not all and the node I was really after was not namespaced itself but was a child of a namespaced node. After I applied the code above I found that I couldn't access the child node:

$simpleXml
    ->children('ns2', true)
    ->namespacedNode
    ->nonNamespacedNode

I thought all was lost until I discovered you can reset SimpleXML's namespace filter back to none again with another call to children() so you can get at the non-namespaced child node:

$simpleXml
    ->children('ns2', true)
    ->namespacedNode
    ->children()
    ->nonNamespacedNode

Sunday, 29 June 2014

Jasmine and Jasq

Recently I've started using Jasmine - a BDD framework - to test some pretty complex JavaScript I've been writing for work. We're using RequireJS for our JavaScript and we found a great plugin for Jasmine to make it play nicely with AMD modules called Jasq.

All works beautifully except for one thing: the Jasq docs say to wrap your specifications in a require block but that didn't seem to work for me. Once I changed them to define blocks (see below) everything started working. I don't know why this is and it doesn't seem anyone else had the same issue so perhaps I've done something wrong but I just thought I'd put this out there in case anyone else runs into the same issue.

define(['jasq'], function ()
{
  describe('My Tests', 'thing/to/test', function()
  {
    it('should do something', function(thingToTest)
    {
        //...
    });
  });
});

Monday, 2 September 2013

Elasticsearch, Chef and Vagrant

I've been tasked at work with setting up an Elasticsearch cluster. We use Chef for provisioning and there's an official cookbook available with some instructions but they pressume you are using Amazon EC2 which we are not - we're using our own servers and Vagrant VMs for testing - so I had to figure a few things out myself.

When I first added the recipe to the node's run list it all installed fine but then I found that Elasticsearch was not running. When I tried running it manually it just said "Killed" and exited. This had me scratching my head for quite a while but I finally found the solution.

In some of the official examples they include the following in the Chef node:

"elasticsearch": {
    "bootstrap.mlockall": true
}

It's not explained what this does but in the template config YAML file it says it prevents the JVM from using swap which causes Elasticsearch to perform badly. Fair enough, however, on a virtual machine that has very little memory it can mean that the JVM doesn't have enough memory to run so it crashes. True is the default value so it's not enough to simply not specify this config, you have to set it to false.

Once I got that working my first node had Elasticsearch running and all was well. Then I started up my second node but I couldn't get it to form a cluster with the first.

As per the documentation I had given them both the same cluster_name. Our servers are spread across different networks so I couldn't use the default multicast option for discovery so I added the FQDN's of each node to the unicast list:

"elasticsearch": {
    "discovery.zen.ping.multicast.enabled": false,
    "discovery.zen.ping.unicast.hosts": "[\"node1.example.com\", \"node2.example.com\"]"
}

Each node has a host entry for each other node and they could telnet to each other on the Elasticsearch discovery port (9300) just fine but when the second node started up I got an error like:

[node2[inet[/ 
10.0.2.2:9300]] failed to send join request to master [node1], reason
[org.elasticsearch.transport.RemoteTransportException: [node2[inet[/ 
10.0.2.2:9300]][discovery/zen/join]; 
org.elasticsearch.ElasticSearchIllegalStateException: Node [node2[inet[/ 
10.0.2.2:9300]] not 
master for join request from [node2[inet[/ 
10.0.2.2:9300]]

Huh? Why was node2 trying to connect to node2? It was my colleague that noticed the references to the 10.0.2.* IPs where we would've expected 192.168.33.* IPs. Turns out that Vagrant always sets the NAT adapter on eth0 and it was the IP of that that Elasticsearch was binding to by default. You can override with the network.host config:

"elasticsearch": {
    "network.host": "192.168.33.1"
}

Once I'd done that for each node (with their respective IPs) the cluster started working.

Monday, 15 July 2013

MongoDB, replica sets and authentication

I've just recently had to set up a MongoDB replica set using Chef at work. It was also required that authentication be set up in MongoDB.

I had very little experience of either technology (but I would now recommend both) so it took me quite a while to figure it all out so I thought I'd offer some advice here for anyone else who may need it. Although I used Chef most of this stuff still applies even if you're doing it manually. I'll be presuming you know what replica sets are, if you don't but are interested please read up on them first.

Many set-ups are possible with replica sets but we opted for a three node set: primary, secondary and an arbiter.
It's also worth noting that I used Vagrant to test all this which I'd highly recommend. It did mean having three VM's running simultaneously which chewed up my RAM but it was worth it.

We used the edelight chef-mongodb cookbook as the starting point. I added recipe[mongodb::replicaset] to all three nodes' run lists. Then came my first stumbling block: by default this recipe will try to initiate the replica set from all three nodes but only the primary can do that so, for the other two nodes, you need to add the following attribute:

{
    "mongodb": {
        "auto_configure": {
            "replicaset": false
        }
    }
}

Then on all three nodes you need to add the following (this can be combined with the other mongodb attributes, it's just shown separately here for convenience):

{
    "mongodb": {
        "cluster_name": "ClusterName", 
        "replicaset_name": "ReplicasetName"
    }
}

And finally on the arbiter node you need to add:

{
    "mongodb": {
        "arbiter": true
    }
}

The next problem was that, at the time of writing, this cookbook does not support authentication. There is a pull request to add keyFile support (which is how you do authentication for replica sets) but it has not been actioned yet. We forked the repository and pulled in these changes ourselves. Once done you then need to generate suitable keyFile contents then specify the following attribute on all three nodes:

{
    "mongodb": {
        "key_file": "KeyFileContentGoesHere"
    }
}

Non-Chef note:
If you're doing this manually you'll need to make sure you start mongo with the following arguments (Chef does this for you):

--replSet ReplicasetName --keyFile /path/to/keyFile

The next issue was that this cookbook has been written for Chef-Server and tries to search for the nodes of the replica set to initiate. Chef-Solo, however, does not support searching. Edelight offer a workaround for this with chef-solo-search. I didn't actually make use of this, we just hard-coded our list of nodes into some attributes but that's a bit hacky.

On to the next problem: the set members contact each other via their domain names. Not all of our servers had domain names set up so we needed to add hosts entries to each server in the set. In Chef we did this with CustomInk's Hostsfile cookbook.

The sequence in which you bring up the replica set nodes is somewhat important in that they must all be operational before you can initiate the set, therefore the primary has to come up last.
I provisioned the arbiter first (which we were already using for something else) and ensured that mongodb was running and that I hadn't broken anything else. I then provisioned our secondary and checked that mongo was running and that it could ping the domain name of the arbiter (which was in the hosts file) and vice-versa. I then provisioned the primary which initiated the replica set. After about a minute it was up and running.

Well... there was a little more to it than that.
We already had the 'primary' MongoDB server live but just as a standalone, we'd later decided to make it a replica set. We'd already set up authentication on the standalone and that's where things got complicated. In order to initiate a replica set on a MongoDB instance with authentication you must be authenticated as a user with the clusterAdmin role. If you don't you will simply get an “unauthenticated” error which, if you're like me, will make you tear your hair out as you're sure you're entering the right credentials.
The other issue is that the edelight cookbook does not handle authenticating before trying to initiate the replica set so we had to add this in. It's very simple, in libraries/mongodb.rb, in the configure_replicaset define, just before the command to initiate the replica set add:

admin.authenticate("username", "password")

As long as that user has the aforementioned clusterAdmin role you should be set.

Monday, 25 February 2013

SimpleXML and large files

I encountered an issue earlier today whilst trying to process a fairly large (~37MB) XML file through SimpleXML.

It worked just fine on mine and a colleague's development systems but failed with weird errors on the live server, for instance reporting there was "Extra content at the end of the document".

After about an hour of trying to figure it out we realised our dev systems were using libxml v2.6.x and the live server was using 2.7.6. We then read that somewhere between those versions some hard-coded limits were added in that can cause the problem we were seeing.

To get around it you need to specify the LIBXML_PARSEHUGE flag:

$simplexml = simplexml_load_file($file_path, 'SimpleXMLElement', LIBXML_PARSEHUGE);
//OR
$simplexml = new SimpleXMLElement($xml_string, LIBXML_PARSEHUGE);

Saturday, 9 February 2013

Documentation using ApiGen and Swagger UI

At work we're writing an API and an SDK that'll talk to that API. We hope to one day make this SDK available to third parties and so we wanted to ensure there was good documentation for it.

I suggested early on that the code itself should be where we write the documentation (in DocBlocks) and then generate external documentation from there. That way both the code and the documentation are complete and consistent with each other.

This suggested the use of something like PHPDocumentor but that's a little long-in-the-tooth and produces rather dry-looking documentation (in our opinion). ApiGen is similar to PHPDoc but is a bit more modern with support for namespaces, traits, etc but still just produces boring HTML by default.

My boss had encountered Swagger UI which is a collection of JavaScript and CSS files that render nice looking docs. The problem is that Swagger is designed specifically for RESTful APIs, not PHP SDKs.

I looked into it though and found at least a partial solution:
ApiGen allow you to write your own templates (PHPDoc also has this) and the input to Swagger UI is just a collection of JSON files and so we decided to write ApiGen templates that produced Swagger UI JSON.

Swagger UI needs an 'index' JSON file that defines what APIs are available and then a JSON file for each of those APIs. In our case we had a file per Class in the SDK. In each of the Class JSON files you then define the (public) methods.

The spec for Swagger UI JSON files is here:

ApiGen templates use Nette Framework Latte Templates: http://doc.nette.org/en/templating

We wrote an overview.latte file that loops all the classes and enumerates them and a class.latte file that loops all the methods of a class and details those.

I said this was a partial solution - we have got all this working nicely but Swagger UI still looks like it's describing a RESTful interface. For example: for every method we defined we had to say whether it was GET, PUT or POST which obviously isn't relevant. That said you can edit the Swagger JavaScript to do whatever you want so you can change things like the above.

Saturday, 12 November 2011

Rename a MySQL database

Recently I needed to rename a MySQL database I was working with to make way for another with the same name. When I looked around for how to do this it seemed that the only option was to create the new database and then copy each table to it, via INSERT...SELECTs or a dump and import. It was quite a large database and this would have been very time consuming.


After discussing it with a colleague we came up with another way to do it by using bash to iterate over each table in the schema and then use the RENAME TABLE command:

for TABLE in `mysql -u[USERNAME] -p[PASSWORD] [OLD DB] -e "SHOW TABLES" -B -N -s`; do mysql -u[USERNAME] -p[PASSWORD] [OLD DB] -e "RENAME TABLE [OLD DB].$TABLE TO [NEW DB].$TABLE"; done;

This worked a treat (I've been using my new database reliably for a while now) and was fast too!

Sunday, 26 September 2010

Symlinks in Windows

Not strictly a LAMP tip this as it's for Windows but I found it useful recently.

I'm using the rather awesome XAMPP to do some development for one of my personal sites on my Windows machine and I wanted to symlink my codebase into XAMPP's equivalent of a 'public_html' folder (they call it 'htdocs'). I tried simply creating a shortcut but this doesn't work as a shortcut isn't a direct pointer but is a file in itself so Apache just tries to read that file.

After much Googling I discovered that, since Vista, Microsoft have introduced a symlinking function, MKLINK:


MKLINK /D C:\path\to\link C:\path\to\target

The /D specifies a link to a directory as the default is to a file.

This worked a treat on my Windows 7 machine! Who knew Vista introduced something useful?! ;)

Monday, 19 April 2010

Colour Schemes

This is more for designers than developers but some of us have to do both :)

When coming up with a colour scheme for a site or UI it's a good idea to use colours that compliment each other. This isn't just a design thing, it's partly mathematical and there are some sites out there that can calculate compatible colours for you.
This is a good example: http://colorschemedesigner.com/

Another thing that can be useful is the ability to blend two colours together. http://meyerweb.com/eric/tools/color-blend/ allows you to input two hex colour values and it will give you the resulting hex colour value.


Friday, 9 April 2010

PHP-GTK Tips

I've been developing a PHP-GTK application over the past few months and have found that there are very few resources out there to help (the entire project seems a bit dead tbh..), however I have come across a few invaluable things that I thought I'd share:


php-gtk2 Cookbook: This is by far the best resource I have found. It has loads of examples and you can even look them up by widget type. It also has a help forum full of useful answers.
You have to register to see all of the code but it's free and I haven't had any spam as a result so I highly recommend it.

Official API documentation: This is a bit feeble but contains all the classes and methods (even if it doesn't explain them very well).
One thing to watch out for with this is that the class pages don't show you inherited methods (even from interfaces) so you have to read through the entire hierarchy to get the full picture.

Glade UI Designer: A handy tool for constructing interfaces for GTK. These produce an XML file which you can then read into your PHP code with the GTKBuilder class. Much nicer than having to create all of your interfaces in code.
Be wary though that it is a bit flakey (crashes sometimes) and it can only do so much for you, some of it will still need to be done in code.

When looking for other resources remember that GTK isn't specific to PHP and so you may find more results for just searching for GTK stuff.

Hope that helped!

Saturday, 10 October 2009

No disk space causes random errors

I'm sure most of you are aware of this but it's something that always takes me a long time to remember.

Whenever you have a seemingly random error on your Linux box - strange MySQL or Apache errors, being unable to write to a file that you could write to a moment ago, etc - always do a quick df -h to check how much disk space you've got left.

This problem is especially evident for those of us developing on virtual machines that often have limited disk space and so can run out easily. A quick recce of your /var/log/ (or equivalent) directory is a good starting point for freeing up space but you may also want to try find / -type f -size +50000k

Wednesday, 12 August 2009

ExtJS Store Reset

I recently discovered something rather annoying about ExtJS Stores.
You may know that you can request all of the modified Records from a Store with something like:

var arr_modified = obj_store.getModifiedRecords();

This can be very useful if you have a lot of Records but only want to perform actions on the ones that have been edited by the user.
However, what I didn't realise is that the modified Records are held separately so that if you empty the Store (by calling removeAll()) and repopulate it then call getModifedRecords() again not only do you get any modified Records from the new batch but from the old batch also.

The remedy to this is quite simple, upon emptying the Store you must also call rejectChanges() which clears the cache of changed Records (this is complimentary to commitChanges() which performs basically the same action but for a different reason).

Friday, 6 February 2009

"shell_exec(): Unable to execute"

I spent ages trying to get round this PHP warning today:

"PHP Warning: shell_exec(): Unable to execute '[any bash command]'"


I hadn't changed anything and it was working 5mins before so what was wrong?

A colleague of mine suggested restarting Apache... and it worked!
I don't fully understand why, something to do with Apache running out of resources or something. Anywho, thought it might be useful for someone.

Tuesday, 10 June 2008

Comet server-push framework

I've just been asked to look into a way to 'stream' information from the server to any listening clients. Basically it's a server-push rather than client-pull system where changes made on one client are (almost) immediately reflected on all other clients. This is similar to AJAX in that there is no page refresh but it is more autonomous.

I've been reading up on Comet, which is basically just a term for a kind of AJAX solution to this problem, but it doesn't specify any implementation suggestions.

Bayeux takes this a step further by actually outlining a protocol for message transfer between client and server.

Most implementations on the server-side are in Java but we need PHP. I've tracked down a PHP implementation called Comep that works quite well on the server-side (although its procedural rather than OO based) and I'm going to look into replacing its vanilla-javascript client-side with this jQuery Comet plugin.

For any other LAMP developers interested in Comet a good place to start is the Comet Daily Blog.

Update, 2008-06-15: Although PERL rather than PHP based it is also worth mentioning Meteor, which is a highly polished comet server implementation.

Thursday, 1 May 2008

Custom ExtJS header buttons

ExtJS Panels can have a limited range of buttons on the header bar but what if you want a custom button? These can be added to a toolbar just underneath the header but if space is at a premium or perhaps you only want one button it's not always ideal.

One solution is to have no header and just make the toolbar look like a header:

var obj_panel = new Ext.Panel({
 tbar: new Ext.Toolbar({
// make the toolbar background look like a header
 cls: 'x-panel-header',
 height: 25,
 items: [
// give the toolbar a title which looks like a Panel title
  '<span style="color:#15428B; font-weight:bold">Title Here</span>',
  '->', //fill element
  {
   text: 'Button Text',
   iconCls: 'add', // optional icon class
   handler: function() {
   // do stuff here
  }
 },' ']
 }),
// ...
});

(The styles used above fit the standard ExtJS theme)

This produces something like this:

Saturday, 19 April 2008

Extending classes in JavaScript

Note: This post was originally written in 2008. There's a 2015 update at the bottom.

There are many different ways to achieve Object Orientation in JavaScript and the Internet is littered with examples.

jQuery Uses an extend method within the super-class that allows you to add functionality onto an instance of that class, creating a kind of sub-class (although it is an object):

function SuperClass() {}
SuperClass.prototype = {
// ...
extend: function() {
 var int_count = 0;
 var arr_prop;
 while ( (arr_prop = arguments[int_count++]) != null ) {
  for ( var i in arr_prop ) {
   this[i] = arr_prop[i];
  }
 }
 // Return the modified object
 return this;
}
}

// ...

var obj_subclass = new SuperClass();
obj_subclass.extend({
foo: 'bar',
// ...
});

This is quick and dirty but not proper OO and if you want several instances of the 'sub-class' you need to call extend on each one.

You probably already know that to add member variables/methods to a class you assign them to its prototype property. This can be one value at a time:

function SuperClass() {}
SuperClass.prototype.foo = 'bar';

Or a simple object can be assigned:

function SuperClass() {}
SuperClass.prototype = {
 foo: 'bar',
 x: 'y',
 super_func: function() {
 //...
 }
}

A good way to simulate inheritance is to assign an instance of the super-class to the sub-class' prototype example here:

var obj_super = new SuperClass();
function SubClass() {}
SubClass.prototype = obj_super;

This means that all member variables/functions of SuperClass are now contained within SubClass' prototype.

In order to now extend SuperClass' functionality in SubClass we revert back to adding one new thing at a time with SubClass.prototype.sub_func = function() { //... }. This is fine but if you plan on adding a lot more stuff it's not ideal.

The solution I have come up with (I say "I", maybe other people do this but I can't find any mention of it on the net) is to combine the two methods above:

var obj_super = new SuperClass();
function SubClass() {}
SubClass.prototype = obj_super.extend({
 foo: 'bar',
 sub_func: function() { //... },
 // ...
});

This way we are still creating a proper sub-class and inheriting the super-classes functionality whilst conveniently adding our own.

Note: You can call super-class methods directly from within the sub-class with SuperClass.prototype.super_func.call(this, arg1, arg2, argN);

Update (03/01/2015)

The above used to be how I extended 'classes' in JavaScript but these days I use a different method. Again, there are many ways to achieve JS extension, what follows is just my preference.

This method has two main requirements:

  1. You call the constructor of the parent inside the constructor of the child using the child's context
  2. You copy the parents prototype onto the child's prototype via the Object.create method
For example:

function SuperClass()
{
    var a = 1;
    this.getA = function()
    {
        return a;
    };
}

SuperClass.prototype.functionA = function()
{
    alert('SuperClass functionA');
};

function SubClass()
{
    SuperClass.call(this);
    var b = 2;
    this.getB = function()
    {
         return b;
    };
}

SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.functionB = function()
{
    var aPlusB = this.getA() + this.getB();
    alert('SubClass functionB. a+b == '+aplusB);
};

var eg = new SubClass();
eg.functionA(); // alerts "SuperClass functionA"
eg.functionB(); // alerts "SubClass functionB. a+b == 3"

Now SubClass extends SuperClass. So why not use SubClass.prototype = new SuperClass(); you ask? Well there's a subtle difference between new and Object.create() in that the latter doesn't actually call SuperClass's constructor, it just copies the prototype so the SuperClass constructor will only get called at the time of creation of the SubClass (as we are calling it explicitly with SuperClass.call(this)).

Note that, as with the old method from this post, the way to call SuperClass function directly is with SuperClass.prototype.functionName.call(this, arg1, argN)

Wednesday, 9 April 2008

Creating a basic select box in ExtJS


I'm currently working on a project that involves generating an entirely ExtJS-based interface from PHP code.

I needed to be able to produce in ExtJS the equivelent of a standard HTML <select> box. This can be achieved with a ComboBox, however, it seems that ComboBoxes are heavily geared towards doing lots of clever things like loading in options over AJAX, allowing the user to type in their own options, auto-completing typed in options, etc. That's all great but if all you want is a standard drop-down with a predefined set of options it takes a bit of work.

I incorrectly assumed that something like the following would work:

obj_combo = new Ext.form.ComboBox({
 name: 'countries',
 items: [
  {value: '1', text: 'UK'},
  {value: '2', text: 'US'},
// ...
]
});

Unfortunaltey, it's not that simple.

Here's an example of the actual code required:
obj_combo = new Ext.form.ComboBox({
 name: 'countries',
// Stop users being able to type in the combobox
 editable: false,
// Even though the user cant type any more
// once they select one option it'll remove any
// others that don't start with the same letters
// unless we turn off filtering

 disableKeyFilter: true,
// Only allow users to pick an option that exists
// in the list of options (not one of their own)

 forceSelection: true,
// This isn't entirely necessary but the combox
// will start off blank otherwise

 emptyText: '--select one--',
// This one's vital: when the user clicks on the
// drop-down show ALL options

 triggerAction: 'all',
// By default it retrieves remote data,
// we're using local data

 mode: 'local',
// ComboBox will only accept data from a Store
// so we have to create a basic one

 store: new Ext.data.SimpleStore({
  id: 0,
  fields: ['value', 'text'],
  data : [['1', 'UK'], ['2', 'US']]
 }),
// Specify which fields in the store hold
// the value and the display text

 valueField: 'value',
 displayField: 'text',
// Important: by default the POST/GET data
// for this item will contain the display text
// not the value. This option creates a hidden field
// with the same name as the dropdown containing the
// selected value so it is that which gets returned

 hiddenName: 'countries'
});

With all those set (plus any of your own) you should have the equivalent of an HTML select box in ExtJS.

For good examples of different ComboBoxes (but unfortunately little explanation of them) check this link:
http://extjs.com/deploy/ext/examples/form/combos.html

Friday, 4 April 2008

Traversing XML in ExtJS

We've just started using the JavaScript ExtJS framework for a new project and whilst it is very good at presentational stuff we have found it to be lacking in the DOM manipulation and traversal department.

We use XML a lot to send data back and forth and so we needed to use that instead of ExtJS' preferred JSON. That in itself is fine, ExtJS provides an XmlReader class that can interpret XML. However, this is geared towards reading in a result set for use in a grid, etc. We wanted to simply return some messages and get ExtJS to read them.
Not impossible I'm sure but we we're struggling with it.

Long story short: we decided to use jQuery for that bit :)
We were already very familiar with jQuery and where it is not as good at presentation as ExtJS it makes up for it in it's superior (imo) DOM traversal and manipulation.

ExtJS and jQuery can co-exist quite peacefully so this was no problem, here's a sample:

Ext.Ajax.request({
 // ..
 // success and failure functions are passed
 // the XMLHTTPRequest object
 success: function(obj_response) {
  var str_val=$('mynode',obj_response.responseXML).text();
  alert(str_val);
 }
 // ...
});
(This assumes that, somewhere, you're including both the ExtJS and jQuery library js files)

This would work for:

<?xml version='1.0'?>
<mynode>This would be the output</mynode>

Again, I'm sure ExtJS can do something similar but I couldn't get it to work.

Bear in mind that other ExtJS classes that can make AJAX calls sometimes pass the success and fail functions another object which contains the XMLHTTPRequest object, so the call is something like obj_response.response.responseXML. Check the ExtJS API Docs for more.

If you are familiar with ExtJS and not jQuery, or vice-versa, I'd highly recommend reading up on both :)

Exclude certain files from a cp command

I needed to be able to exclude files with a certain extension from a cp command that was going to be executed automatically as part of a much larger script.

There doesn't seem to be any direct way to do this (e.g. cp --exclude...) and although there are several suggestions on the net on how to overcome this I found the neatest way was to use the tar command instead:

cd /copy/from/path
tar -czf /path/to/archive.tar.gz ./ --exclude=*.txt
cd /copy/to/path
tar -xzvf /path/to/archive.tar.gz

Of course this isn't ideal, creating a tar archive when it isn't necessary but the files I needed to copy weren't large and so it was nice and quick and dirty.