NAME
CGI::Uploader::Cookbook - Examples of CGI::Uploader usage
Description
CGI::Uploader::Cookbook
is a tutorial that accompanies the CGI::Uploader distribution. It shows example syntax for common uses.
CGI::Uploader
module is designed to help with the task of managing files uploaded through a CGI application. The files are stored on the file system, and the file attributes stored in a SQL database.
Introduction to CGI::Uploader
A Little History
The release of this module represents a culmination of seven years of experience mananging file uploads as a professional website developer for Summersault, LLC (http://www.summersault.com/). Over that time I noticed patterns that were re-usable from project to project. I went through serveral versions and rewrites of modules that attempted to be 'generic' and not need modification when the next project came along. With CGI::Uploader, I believe I finally have a solution that I will continue to be happy with and I think others will be find generally useful. Enjoy!
Freedom of Choice
I have strived to make CGI::Uploader to work within a variety of system designs. It offers you freedom choice in the following areas:
Database Choice
MySQL and Postgres are supported directly. The SQL used is very simple-- support for additional databases should be trivial.
Choice of Query Provider
The query object used may provided by
CGI.pm
,CGI::Simple
orApache::Request
. Another source could be used by overriding theupload
method.File Storage Schemes for Large and Small Projects
For small projects, all uploads can be stored in a single directory. For large projects, we provide the
md5
file scheme, which should scale well to millions of images, without burdening any single directory with storing too many of them.Choice of Data Display
Because the meta data is stored in a straightforward SQL database table, you can retrieve your data and display in any number of custom ways. Several functions are also built in to help with common display tasks. The
build_loc()
method is used to construct the file system or URL path of an image, given it's ID and extension.fk_meta()
provides an easy way to get the meta data of an upload by relating it to a foreign key in another table.Finally,
transform_meta()
is a basic function which tranforms a hashref of data from the database into a format more useful for display, producing a hash that looks like this:{ my_custom_prefix_id => 523, my_custom_prefix_url => 'http://localhost/images/uploads/523.pdf', my_custom_prefix_width => 23, my_custom_prefix_height => 46, }
Image Processor
While
CGI::Uploader
works with all types of file uploads, it contains a number of features to help with common tasks associated with image uploads.Image::Magick
is the preferred image processing module for to use when creating the thunbmails. Support forGD
is in progress.GD
supports many fewer formats, but also has much fewer dependencies to get installed thanImage::Magick
does. Another providers could be used by extending or overriding thegen_thumb()
method.
Just Three Essential Methods to Learn
A goal of <CGI::Uploader> is to provide a high-level interface to make managing file uploads easy. Only three methods are needed to manage all the functions needed to store, update, delete and view the uploads attached to sme database entity. Those methods are store_uploads()
, delete_checked_uploads()
and fk_meta
.
More methods when you need them
When your needs before more complex, you can call the lower-level functions in CGI::Uploader
to meet your needs. Most functions use file names to access file uploads, so it's easy to use the module to manipulate files from other sources than the browser upload field.
For example, the gen_thumb()
method is general purpose thumbnail creating routine.
Browse, Read, Edit, Add, Delete (BREAD) Example Application
Before CGI::Uploader
can be useful, some setup needs to be done. You need some database tables to store the information in.
Example Database
For these examples, we'll set up some tables to manage photos of friends. Here is the SQL to create such tables with Postgres:
-- Note the Postgres specific syntax here
CREATE SEQUENCE upload_id_seq;
CREATE TABLE uploads (
upload_id int primary key not null
default nextval('upload_id_seq'),
mime_type character varying(64),
extension character varying(8), -- file extension
width integer,
height integer,
thumbnail_of_id integer,
)
CREATE TABLE address_book (
friend_id int primary key,
full_name varchar(64),
-- these two reference uploads('upload_id'),
photo_id int,
photo_thumbnail_id int
);
(MySQL is also supported. Check in the distribution for sample SQL 'Create' scripts for both MySQL and Postgresql databases).
Object Creation
You can create one CGI::Uploader
object and use it for adding, updating, viewing and deleting uploads. So don't despair that it has a few required parameters-- you only need to type them once! :)
my $u = CGI::Uploader->new(
spec => {
photo => [
{ name => 'photo_thumbnail', w => 100, h => 100, }
],
}
updir_url => 'http://localhost/uploads',
updir_path => '/home/friends/www/uploads',
dbh => $dbh,
);
Adding a Database Record and Related Uploads
Before we can do anything else with the uploads, we need to get some added into the system.
CGI::Uploader
is designed to make this happening easily as part of the normal process of adding a normal database record. In this case, we'll be adding a friend.
Example 'Add Form'
Here's the form used to add a friend. It includes fields for the friend's name, and a photo of them.
<form action="your-script.cgi" enctype="multipart/form-data" METHOD="POST">
Friend Name: <input type="text" name="full_name"> <br />
Image: <input type="file" name="photo">
<input type="submit">
</form>
Notice that the 'enctype' is important for file uploads to work.
Notice we have a text field for a 'full_name' and a file upload field named 'photo'.
Processing the Add Form
AS a first step for processing the 'add form', I recommend validating the form with Data::FormValidator. It includes several routines just to validate file uploads. However, it's not necessary to validate the form.
# CGI::Simple provides a CGI.pm-like interface with much better performance
use CGI::Simple;
my $q = CGI::Simple->new();
my $form = $q->Vars;
my $friend = $u->store_uploads($form);
# Now the $friend hash been transformed so it can easily inserted
# It now looks like this:
# {
# full_name => 'M. Lewis',
# photo_id => 3,
# photo_thumbnail_id => 4,
# }
# I like to use SQL::Abstract for easy inserts.
use SQL::Abstract;
my $sql = SQL::Abstract->new;
my($stmt, @bind) = $sql->insert('address_book',$friend);
$dbh->do($stmt,{},@bind);
Database Result of Adding
Here's what ended up in the database:
address_book table:
friend_id | full_name | photo_id | photo_thumbnail_id
-----------------------------------------------------
2 | M. Lewis | 3 | 4
uploads table:
upload_id | mime_type | extension | width | height | thumbnail_of_id
--------------------------------------------------------------------
3 | image/png | .png | 200 | 400 |
4 | image/png | .png | 50 | 100 | 3
The files are stored on the file system. '4.png' was generated on the server a thumbnail of 3.png.
/home/friends/www/uploads/3.png
/home/friends/www/uploads/4.png
Displaying & Linking to Uploads
You don't strictly need this module to display the uploaded image. You could construct your own database queries and URLs instead. However, the fk_meta
method is provided to simplify things for you.
Continuing with the example above, we would use this code to generate the details we need to display and link to the photo and thumbnail:
my $href = $u->fk_meta('address_book',{ friend_id => 2 },qw/photo photo_thumbnail/);
That will fetch the details of the photo and thumbnail associated with the friend who is an ID of "2".
The resulting hashref will look something like this:
{
photo_id => 3,
photo_url =>'http://localhost/uploads/3.png?23',
photo_width => 200,
photo_height => 400',
photo_thumbnail_id => 4,
photo_thumbnail_url =>'http://localhost/uploads/4.png?23',
photo_thumbnail_width => 50,
photo_thumbnail_height => 200',
}
This hashref can often be passed directly to a templating sytem such as HTML::Template for display.
You may be wondering about the query strings on the URLS. These are random numbers to defeat browser image caching, which is very useful on upgrade forms. This behavior may change or become optional in a future release.
Displaying an Update Form
So now we've added 'M. Lewis' to our friend database and displayed his photo on the web. M. Lewis turned out not to be happy about this. He reports that the photo used was not his 'good side' and has sent a 'better' photo to use.
So now we need to have a form to update the photo from.
The form to update the upload will be a lot like the 'add form'. Additionally, it's nice to display a link to current upload on the form. This can be done using fk_meta
, as demonstrated above.
Our Update Form might look like this if we are using HTML::Template for display:
<form action="your-script.cgi" enctype="multipart/form-data" METHOD="POST">
<P>Friend Name: <input type="text" name="full_name"> </p>
<P>
Current Image: <!-- tmpl_var photo_url --> <br/>
<input type="checkbox" value="1" name="photo_delete"> Delete Image?
</P>
<input type="hidden" name="photo_id" value="<tmpl_var photo_id>">
<p>Image: <input type="file" name="photo"></p>
<input type="submit">
</form>
Processing an Update Form
Processing an update form is the most complicated part of application. From this form it's possible to add, update and delete uploads
To process the update form, we'll first delete any uploads that the user has requested to remove. Next, add and update any other uploads as need.
my $friend = $q->Vars;
my @fk_names = $u->delete_checked_uploads;
map { $friend->{$_} = undef } @fk_names;
delete $friend->{photo_delete};
$friend = $u->store_uploads($friend);
Although the call to store_uploads()
looks the same as it did for adding a record, it works a little different now. Notice we passed a photo_id through the form above. Because this is present, that record will be updated instead of creating a new one.
Recipe Idea: Handling anonymous image uploads
The recipe above required having a column name in another table for each upload that was stored. It's also possible with CGI::Uploader to have many "anynonmous" uploads associated with another entity in the database.
[ And the documentation for how to that still needs to be written. :) ]
Recipe Idea: Further automation
CGI::Uploader
Allows you to build your specification on the fly, when the exact fields you may need to process may vary. To do this, the keys defined in the spec
can regular expressionss. The simplest one wuld automatically process all file upload fields delivered through the form.
spec => { qr/.*/ => [] },
If you generate thumbnails this case, you'll need a way to give the thumbnails for each fields unique names. To do this, you can define the name as a code reference. It will receive the name of the parent file field as it's first argument:
spec => {
qr/^photos_/ => [
{
name => sub { my $n = shift; "$n_thumb" },
w => 200,
h => 200,
}
] ,
},
For a matching field named photo_2
, the resulting thumbnail would be named photo_2_thumb
.
See Also
Author
Mark Stosberg <mark@summersault.com>