Put routines to perform those operations in a library file, and have your programs access the library. Then write the code only once. You might need to set an environment variable so that your scripts can find the library.
This section describes how to put code for common operations in
library files.
Encapsulation (or modularization) isn’t really a
“recipe” so much as a programming technique. Its
principal benefit is that you don’t have to repeat code in each
program you write. Instead, you just call a routine that’s in the
library. For example, by putting the code for connecting to the
cookbook
database into a library
routine, you need not write out all the parameters associated with
making that connection. Simply invoke the routine from your program,
and you’re connected.
Connection establishment isn’t the only operation you can
encapsulate, of course. Later sections in this book develop other
utility functions to be placed in library files. All such files,
including those shown in this section, can be found under
the
lib directory of
the recipes
distribution. As you
write your own programs, you’ll probably identify several operations
that you perform often and that are good candidates for inclusion in a
library. The techniques demonstrated in this section will help you
write your own library files.
Library files have other benefits besides making it easier to write programs. They can help portability. For example, if you write connection parameters directly into each program that connects to the MySQL server, you have to change all those programs if you move them to another machine that uses different parameters. If instead you write your programs to connect to the database by calling a library routine, you localize the changes that need to be made: it’s necessary to modify only the affected library routine, not all the programs that use it.
Code encapsulation also can improve security in some ways. If you make a private library file readable only to yourself, only scripts run by you can execute routines in the file. Or suppose that you have some scripts located in your web server’s document tree. A properly configured server will execute the scripts and send their output to remote clients. But if the server becomes misconfigured somehow, the result can be that your scripts are sent to clients as plain text, thus displaying your MySQL username and password. (And you’ll probably realize it too late. Oops.) If the code for establishing a connection to the MySQL server is placed in a library file that’s located outside the document tree, those parameters won’t be exposed to clients.
Be aware that if you install a library file to be readable by your web server, you don’t have much security should you share the web server with other developers. Any of those developers can write a web script to read and display your library file because, by default, the script runs with the permissions of the web server and thus will have access to the library.
The examples of programs that follow demonstrate how to write,
for each API, a library file that contains a routine for connecting to
the cookbook
database on the MySQL
server. The calling program can use the error-checking techniques
discussed in Checking for Errors to determine whether
a connection attempt fails. The connection routine for each language
except PHP returns a database handle or connection object when it
succeeds or raises an exception if the connection cannot be
established. The PHP routine returns an object that represents a
connection or an error, because that is what the PEAR DB connection
method does (it does not raise an exception).
Libraries are of no utility in themselves, so each one’s use is illustrated by a short “test harness” program. You can use any of these harness programs as the basis for creating new programs of your own: make a copy of the file and add your own code between the connect and disconnect calls.
Library file writing involves not only the question of what to put in the file but also subsidiary issues such as where to install the file so it can be accessed by your programs, and (on multiuser systems such as Unix) how to set its access privileges so its contents aren’t exposed to people who shouldn’t see it.
If you install a library file in a directory that a language processor searches by default, programs written in that language need do nothing special to access the library. However, if you install a library file in a directory that the language processor does not search by default, you’ll have to tell your scripts how to find the library. There are two common ways to do this:
Most languages provide a statement that can be used within a script to add directories to the language processor search path. This requires that you modify each script that needs the library.
You can set an environment or configuration variable that changes the language processor search path. This approach requires that each user who uses scripts that require the library to set the appropriate variable. Alternatively, if the language processor has a configuration file, you might be able to set a parameter in the file that affects scripts globally for all users.
We’ll use the second approach. For our API languages, the following table shows the relevant variables. In each case, the variable value is a directory or list of directories.
For general information on setting environment variables, see Appendix B. You can use those instructions to set environment variables to the values in the following discussion.
Suppose that you want to install library files in a directory that language processors do not search by default. For purposes of illustration, let’s use /usr/local/lib/mcb on Unix or C:libmcb on Windows. (To put the files somewhere else, adjust the pathnames in the variable settings accordingly. For example, you might want to use a different directory, or you might want to put libraries for each language in separate directories.)
Under Unix, if you put Perl library files in the /usr/local/lib/mcb directory, you can set
the PERL5LIB
environment
variable. For a shell in the
Bourne shell family (sh, bash, ksh), set the variable like this in the
appropriate startup file:
export PERL5LIB=/usr/local/lib/mcb
If you are using the original Bourne shell, sh, you may need to split this into two commands:
PERL5LIB=/usr/local/lib/mcb export PERL5LIB
For a shell in the
C shell family (csh, tcsh), set PERL5LIB
like this in your .login file:
setenv PERL5LIB /usr/local/lib/mcb
Under Windows, if you put Perl library files in C:libmcb, you can set PERL5LIB
as follows:
PERL5LIB=C:libmcb
In each case, the variable setting tells Perl to look in the
specified directory for library files, in addition to whatever other
directories it would search by default. If you set PERL5LIB
to name multiple directories, the
separator character between directory pathnames is
colon (:
) on Unix
or
semicolon (;
) on
Windows.
The other environment variables (RUBYLIB
, PYTHONPATH
, and CLASSPATH
) are specified using the same
syntax.
Setting these environment variables as just discussed should suffice for scripts that you run from the command line. But for scripts that are intended to be executed by a web server, you’ll likely have to configure the server as well so that it can find the library files. See Using Apache to Run Web Scripts for details on how to do this.
For PHP, the search path is defined by the value of the
include_path
variable in
the
php.ini
PHP initialization file. On Unix, the file’s pathname
is likely to be /usr/lib/php.ini or /usr/local/lib/php.ini. Under Windows,
the file is likely to be found in the Windows directory or under the
main PHP installation directory. The value of include_path
is defined with a line like
this:
include_path = "value
"
value
is specified using the same
syntax as for environment variables that name directories. That is,
it’s a list of directory names, with the names separated by colons
on Unix or semicolons on Windows. For example, on Unix, if you want
PHP to look for include files in the current directory and in
/usr/local/lib/mcb, set
include_path
like this:
include_path = ".:/usr/local/lib/mcb"
On Windows, to search the current directory and C:libmcb, set include_path
like this:
include_path = ".;C:libmcb"
If you modify the php.ini file, and PHP is running as an Apache module, you’ll need to restart Apache to make your changes take effect.
Questions about file ownership and access mode are issues about which you’ll need to make decisions if you’re using a multiple-user system such as Unix:
If a library file is private and contains code to be used only by you, the file can be placed under your own account and made accessible only to you. Assuming that a library file mylib is already owned by you, you can make it private like this:
%chmod 600 mylib
If the library file is to be used only by your web server,
you can install it in a server library directory and make the
file owned by and accessible only to the server user ID. You may
need to be root
to do this.
For example, if the web server runs as wwwusr
, the following commands make
the file private to that user:
#chown wwwusr mylib
#chmod 600 mylib
If the library file is public, you can place it in a
location that your programming language searches automatically
when it looks for libraries. (Most language processors search
for libraries in some default set of directories.) You may need
to be root
to install files
in one of these directories. Then you can make the file
world-readable:
#chmod 444 mylib
Now let’s construct a library for each API. Each section here demonstrates how to write the library file itself and discusses how to use the library from within programs.
In Perl, library files are called
modules, and typically have an extension of .pm (“Perl module”). Here’s
a sample module file, Cookbook.pm, that implements a module
named Cookbook
. (It’s
conventional for the basename of a Perl module file to be the same
as the identifier on the package
line in the file.)
package Cookbook; # Cookbook.pm - library file with utility method for connecting to MySQL # via Perl DBI module use strict; use warnings; use DBI; my $db_name = "cookbook"; my $host_name = "localhost"; my $user_name = "cbuser"; my $password = "cbpass"; my $port_num = undef; my $socket_file = undef; # Establish a connection to the cookbook database, returning a database # handle. Raise an exception if the connection cannot be established. sub connect { my $dsn = "DBI:mysql:host=$host_name"; my %conn_attrs = (PrintError => 0, RaiseError => 1, AutoCommit => 1); $dsn .= ";database=$db_name" if defined $db_name; $dsn .= ";mysql_socket=$socket_file" if defined $socket_file; $dsn .= ";port=$port_num" if defined $port_num; return (DBI->connect ($dsn, $user_name, $password, \%conn_attrs)); } 1; # return true
The module encapsulates the code for establishing a connection
to the MySQL server into a connect()
method, and the package
identifier establishes a Cookbook
namespace for the module, so you
invoke the connect()
method using the module name:
$dbh = Cookbook::connect ();
The final line of the module file is a statement that trivially evaluates to true. This is needed because Perl assumes that something is wrong with a module and exits after reading it if the module doesn’t return a true value.
Perl locates library files by searching through the
directories named in its @INC
array.
This array contains a default list of directories. To check the
value of this variable on your system, invoke Perl as follows at the
command line:
%perl -V
The last part of the output from the command shows the
directories listed in the @INC
array. If you install a library file in one of those directories,
your scripts will find it automatically. If you install the module
somewhere else, you need to tell your scripts where to find it by
setting the PERL5LIB
environment
variable, as discussed earlier in the introduction to this
recipe.
After installing the Cookbook.pm module, try it from a test harness script harness.pl written as follows:
#!/usr/bin/perl # harness.pl - test harness for Cookbook.pm library use strict; use warnings; use Cookbook; my $dbh; eval { $dbh = Cookbook::connect (); print "Connected "; }; die "$@" if $@; $dbh->disconnect (); print "Disconnected ";
harness.pl has no use
DBI
statement. It’s not necessary because the
Cookbook.php library file itself imports the
DBI module, so any script that uses Cookbook
also gains access to DBI.
If you don’t want to bother catching connection errors
explicitly, you can write the body of the script more simply. In
this case, Perl will catch any connection exception and terminate
the script after printing the error message generated by the
connect()
method:
my $dbh = Cookbook::connect (); print "Connected "; $dbh->disconnect (); print "Disconnected ";
The following Ruby library file, Cookbook.rb, defines a Cookbook
class that implements a connect
method:
# Cookbook.rb - library file with utility method for connecting to MySQL # via Ruby DBI module require "dbi" # Establish a connection to the cookbook database, returning a database # handle. Raise an exception if the connection cannot be established. class Cookbook @@host = "localhost" @@db_name = "cookbook" @@user_name = "cbuser" @@password = "cbpass" # class method for connecting to server to access # cookbook database; returns database handle object. def Cookbook.connect return DBI.connect("DBI:Mysql:host=#{@@host};database=#{@@db_name}", @@user_name, @@password) end end
The connect
method is
defined in the library as Cookbook.connect
because Ruby class
methods are defined as
class_name.method_name
.
Ruby locates library files by searching through the
directories named in its
$LOAD_PATH
variable (also known as $:
),
which is an array that contains a default list of directories. To
check the value of this variable on your system, use Ruby to execute
this statement:
puts $LOAD_PATH
If you install a library file in one of those directories,
your scripts will find it automatically. If you install the file
somewhere else, you need to tell your scripts where to find it by
setting the
RUBYLIB
environment variable, as discussed earlier in the introduction to
this recipe.
After installing the Cookbook.rb library file, try it from a test harness script harness.rb written as follows:
#!/usr/bin/ruby -w # harness.rb - test harness for Cookbook.rb library require "Cookbook" begin dbh = Cookbook.connect print "Connected " rescue DBI::DatabaseError => e puts "Cannot connect to server" puts "Error code: #{e.err}" puts "Error message: #{e.errstr}" exit(1) end dbh.disconnect print "Disconnected "
harness.rb has no require
statement for the DBI module. It’s
not necessary, because the Cookbook
module itself imports DBI,
so any script that imports Cookbook
also gains access to DBI.
If you just want a script to die if an error occurs without checking for an exception yourself, write the body of the script like this:
dbh = Cookbook.connect print "Connected " dbh.disconnect print "Disconnected "
The contents of PHP library files are written like
regular PHP scripts. You can write such a file, Cookbook.php, that implements a Cookbook
class with a
connect()
method as follows:
<?php # Cookbook.php - library file with utility method for connecting to MySQL # via PEAR DB module require_once "DB.php"; class Cookbook { # Establish a connection to the cookbook database, returning a # connection object, or an error object if an error occurs. function connect () { $dsn = array ( "phptype" => "mysqli", "username" => "cbuser", "password" => "cbpass", "hostspec" => "localhost", "database" => "cookbook" ); return (DB::connect ($dsn)); } } # end Cookbook ?>
Although most PHP examples throughout this book don’t show the
<?php
and ?>
tags, I’ve shown them as part of
Cookbook.php here to emphasize
that library files must enclose all PHP code within those tags. The
PHP interpreter doesn’t make any assumptions about the contents of a
library file when it begins parsing it because you might include a
file that contains nothing but HTML. Therefore, you must use
<?php
and ?>
to specify explicitly which parts of
the library file should be considered as PHP code rather than as
HTML, just as you do in the main script.
PHP looks for libraries by searching the directories named in
the value of the
include_path
variable in the PHP initialization file, as described earlier in the
introduction to this recipe. Assuming that Cookbook.php is installed in one of those
directories, you can access it from a test harness script harness.php written as follows:
<?php # harness.php - test harness for Cookbook.php library require_once "Cookbook.php"; $conn =& Cookbook::connect (); if (PEAR::isError ($conn)) die ("Cannot connect to server: " . $conn->getMessage () . " "); print ("Connected "); $conn->disconnect (); print ("Disconnected "); ?>
harness.php has no
statement to include DB.php. It’s
not necessary because the Cookbook
module itself includes DB.php, which gives any script that
includes Cookbook.php access to
DB.php.
Python libraries are written as modules and referenced from
scripts using
import
or from
statements. To create a method for
connecting to MySQL, we can write a module file Cookbook.py:
# Cookbook.py - library file with utility method for connecting to MySQL # via MySQLdb module import MySQLdb host_name = "localhost" db_name = "cookbook" user_name = "cbuser" password = "cbpass" # Establish a connection to the cookbook database, returning a connection # object. Raise an exception if the connection cannot be established. def connect (): return MySQLdb.connect (db = db_name, host = host_name, user = user_name, passwd = password)
The filename basename determines the module name, so the
module is called Cookbook
. Module methods are accessed through the module name;
thus you would invoke the connect()
method of the Cookbook
module like this:
conn = Cookbook.connect ();
The Python interpreter searches for modules in directories
named in the
sys.path
variable.
You can find out what the default value of sys.path
is on your system by running
Python interactively and entering a couple of commands:
%python
>>>import sys
>>>sys.path
If you install Cookbook.py
in one of the directories named by sys.path
, your scripts will find it with
no special handling. If you install Cookbook.py somewhere else, you’ll need to
set the
PYTHONPATH
environment variable, as discussed earlier in the introduction to
this recipe.
After installing the Cookbook.py library file, try it from a test harness script harness.py written as follows:
#!/usr/bin/python # harness.py - test harness for Cookbook.py library import sys import MySQLdb import Cookbook try: conn = Cookbook.connect () print "Connected" except MySQLdb.Error, e: print "Cannot connect to server" print "Error code:", e.args[0] print "Error message:", e.args[1] sys.exit (1) conn.close () print "Disconnected"
The Cookbook.py file
imports the MySQLdb module, but a script that imports Cookbook
does not thereby gain access to
MySQLdb. If the script needs MySQLdb-specific
information (such as MySQLdb.Error
), the script must also
import MySQLdb.
If you just want a script to die if an error occurs without checking for an exception yourself, write the body of the script like this:
conn = Cookbook.connect () print "Connected" conn.close () print "Disconnected"
Java library files are similar to Java programs in most ways:
Java library files also differ from Java programs in some ways:
Unlike regular program files, Java library files have no
main()
function.
A library file should begin with a package
identifier that specifies the
location of the class within the Java namespace.
A common convention for Java package identifiers is to begin
them with the reverse domain of the code author; this helps make identifiers
unique and avoids conflict with classes written by other authors.
Domain names proceed right to left from more general to more
specific within the domain namespace, whereas the Java class
namespace proceeds left to right from general to specific. Thus, to
use a domain as the prefix for a package name within the Java class
namespace, it’s necessary to reverse it. In my case, the domain is
kitebird.com, so if I write a
library file and place it under mcb
within my domain’s namespace, the
library begins with a package
statement like this:
package com.kitebird.mcb;
Java packages developed for this book are placed within the
com.kitebird.mcb
namespace to
ensure their uniqueness in the package namespace.
The following library file, Cookbook.java, defines a Cookbook
class that implements a
connect()
method for connecting to the cookbook
database. connect()
returns a Connection
object if
it succeeds, and throws an exception otherwise. To help the caller
deal with failures, the Cookbook
class also defines
getErrorMessage()
and printErrorMessage()
utility
methods that return the error message as a string or print it to
System.err
.
// Cookbook.java - library file with utility method for connecting to MySQL // via MySQL Connector/J package com.kitebird.mcb; import java.sql.*; public class Cookbook { // Establish a connection to the cookbook database, returning // a connection object. Throw an exception if the connection // cannot be established. public static Connection connect () throws Exception { String url = "jdbc:mysql://localhost/cookbook"; String user = "cbuser"; String password = "cbpass"; Class.forName ("com.mysql.jdbc.Driver").newInstance (); return (DriverManager.getConnection (url, user, password)); } // Return an error message as a string public static String getErrorMessage (Exception e) { StringBuffer s = new StringBuffer (); if (e instanceof SQLException) // JDBC-specific exception? { // print general message, plus any database-specific message s.append ("Error message: " + e.getMessage () + " "); s.append ("Error code: " + ((SQLException) e).getErrorCode () + " "); } else { s.append (e + " "); } return (s.toString ()); } // Get the error message and print it to System.err public static void printErrorMessage (Exception e) { System.err.println (Cookbook.getErrorMessage (e)); } }
The routines within the class are declared using the
static
keyword,
which makes them class methods rather than instance methods. That is
done here because the class is used directly rather than creating an
object from it and invoking the methods through the object.
To use the Cookbook.java
file, compile it to produce Cookbook.class, and then install the
class file in a directory that corresponds to the package
identifier. This means that Cookbook.class should be installed in a
directory named com/kitebird/mcb (or comkitebirdmcb
under Windows) that is located under some directory named in your
CLASSPATH
setting. For example, if CLASSPATH
includes /usr/local/lib/mcb under Unix, you can
install Cookbook.class in the
/usr/local/lib/mcb/com/kitebird/mcb
directory. (See the Java discussion in Connecting, Selecting a Database, and Disconnecting for more information about the
CLASSPATH
variable.)
To use the Cookbook
class
from within a Java program, import it, and then invoke the Cookbook.connect()
method. The
following test harness program, Harness.java, shows how to do
this:
// Harness.java - test harness for Cookbook library class import java.sql.*; import com.kitebird.mcb.Cookbook; public class Harness { public static void main (String[] args) { Connection conn = null; try { conn = Cookbook.connect (); System.out.println ("Connected"); } catch (Exception e) { Cookbook.printErrorMessage (e); System.exit (1); } finally { if (conn != null) { try { conn.close (); System.out.println ("Disconnected"); } catch (Exception e) { String err = Cookbook.getErrorMessage (e); System.out.println (err); } } } } }
Harness.java also shows
how to use the error message utility methods from the Cookbook
class when a
MySQL-related exception occurs:
3.135.247.181