• Aucun résultat trouvé

Writing Library Files

Dans le document MySQL Cookbook (Page 79-93)

Beware of Class.forName()!

2.3. Writing Library Files

}

2.3. Writing Library Files

Problem

You notice that you’re repeating code to perform common operations in multiple programs.

Solution

Write routines to perform those operations, put them in a library file, and arrange for your programs to access the library. This enables you to write the code only once. You might need to set an environment variable so that your scripts can find the library.

Discussion

This section describes how to put code for common operations in library files. Encap‐

sulation (or modularization) isn’t really a “recipe” so much as a programming technique.

Its principal benefit is that you need not repeat code in each program you write. Instead, simply 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, are located under the lib directory of the recipes distribution. As you write your own programs, be on the lookout for operations that you perform often and that are good candidates for inclusion in a library. Use the techniques in this section to write your own library files.

Library files have other benefits besides making it easier to write programs, such as promoting portability. If you write connection parameters directly into each program that connects to the MySQL server, you must change all those programs if you move them to another machine that uses different parameters. If instead you write your pro‐

grams to connect to the database by calling a library routine, it’s necessary only to modify the affected library routine, not all the programs that use it.

Code encapsulation can also improve security. If you make a private library file readable only to yourself, only scripts run by you can execute routines in the file. Or suppose

2.3. Writing Library Files | 51

that you have some scripts located in your web server’s document tree. A properly configured server executes the scripts and sends their output to remote clients. But if the server becomes misconfigured somehow, the result can be that it sends your scripts to clients as plain text, thus displaying your MySQL username and password. (And you’ll probably realize it too late. Oops.) If you place the code for establishing a connection to the MySQL server in a library file 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 if other developers use the same server. 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 recipes that follow demonstrate how to write, for each API, a library file that con‐

tains a routine for connecting to the cookbook database on the MySQL server. The calling program can use the error-checking techniques discussed in Recipe 2.2 to de‐

termine whether a connection attempt fails. The connection routine for each language returns a database handle or connection object when it succeeds or raises an exception if the connection cannot be established.

Libraries are of no utility in themselves, so the following discussion illustrates each one’s use by a short “test harness” program. To use any of these harness programs as the basis for creating new programs, 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 is accessible 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.

Choosing a library-file installation location

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. How‐

ever, if you install a library file in a directory that the language processor does not search by default, you must tell your scripts how to find it. There are two common ways to do this:

• Most languages provide a statement that can be used within a script to add direc‐

tories 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. With this approach, each user who executes scripts that 52 | Chapter 2: Writing MySQL-Based Programs

require the library must 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:

Language Variable name Variable type Perl PERL5LIB Environment variable Ruby RUBYLIB Environment variable PHP include_path Configuration variable Python PYTHONPATH Environment variable Java CLASSPATH Environment variable

For general information on setting environment variables, read “Executing Programs from the Command Line” on the companion website (see the Preface). 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 and C:\lib\mcb 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 direc‐

tory, 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, set the PERL5LIB environment variable appropriately. 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

For 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:\lib\mcb, set PERL5LIB as follows:

PERL5LIB=C:\lib\mcb

In each case, the variable value tells Perl to look in the specified directory for library files, in addition to any other directories it searches by default. If you set PERL5LIB to

2.3. Writing Library Files | 53

name multiple directories, the separator character between directory pathnames is colon (:) on Unix or semicolon (;) on Windows.

Specify the other environment variables (RUBYLIB, PYTHONPATH, and CLASSPATH) using the same syntax.

Setting these environment variables as just discussed should suffice for scripts that you run from the command line. For scripts intend‐

ed to be executed by a web server, you likely must configure the server as well so that it can find the library files. See Recipe 18.2.

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 /usr/lib/php.ini or / usr/local/lib/php.ini. Under Windows, the file is likely found in the Windows directory or under the main PHP installation directory. To determine the location, run this commmand:

% php --ini

Define the value of include_path in php.ini with a line like this:

include_path = "value"

Specify value 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. 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:\lib\mcb, set include_path like this:

include_path = ".;C:\lib\mcb"

If PHP is running as an Apache module, restart Apache to make php.ini changes take effect.

Setting library-file access privileges

If you use a multiple-user system such as Unix, you must make decisions about library-file ownership and access mode:

• If a library file is private and contains code to be used only by you, place the file under your own account and make it accessible only to you. Assuming that a library file named mylib is already owned by you, you can make it private like this:

% chmod 600 mylib

54 | Chapter 2: Writing MySQL-Based Programs

• If the library file is to be used only by your web server, install it in a server library directory and make it 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 pro‐

cessors search for libraries in some default set of directories, although this set can be influenced by setting environment variables as described previously.) 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.

Perl

In Perl, library files are called modules and typically have an extension of .pm (“Perl module”). It’s conventional for the basename of a module file to be the same as the identifier on the package line in the file. The following file, Cookbook.pm, implements a module named Cookbook:

package Cookbook;

# Cookbook.pm: library file with utility method for connecting to MySQL

# using the 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};

2.3. Writing Library Files | 55

$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. To invoke the connect() method, use the module name:

$dbh = Cookbook::connect ();

The final line of the module file is a statement that trivially evaluates to true. (If the module doesn’t return a true value, Perl assumes that something is wrong with it and exits.)

Perl locates library files by searching the list of directories named in its @INC array. To check the default 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 @INC. If you install a library file in one of those directories, your scripts will find it automatically.

If you install the module somewhere else, tell your scripts where to find it by setting the PERL5LIB environment variable, as discussed in the introductory part of this recipe.

After installing the Cookbook.pm module, try it from a test harness script, harness.pl:

#!/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\n";

};

die "$@" if $@;

$dbh->disconnect ();

print "Disconnected\n";

harness.pl has no useDBI statement. It’s unnecessary because the Cookbook module itself imports DBI; any script that uses Cookbook also gains access to DBI.

56 | Chapter 2: Writing MySQL-Based Programs

If you don’t catch connection errors explicitly with eval, you can write the script body more simply:

my $dbh = Cookbook::connect ();

print "Connected\n";

$dbh->disconnect ();

print "Disconnected\n";

In this case, Perl catches any connection exception and terminates the script after print‐

ing the error message generated by the connect() method.

Ruby

The following Ruby library file, Cookbook.rb, defines a Cookbook class that implements a connect class method:

# Cookbook.rb: library file with utility method for connecting to MySQL

# using the 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_name = "localhost"

@@db_name = "cookbook"

@@user_name = "cbuser"

@@password = "cbpass"

# Class method for connecting to server to access the # cookbook database; returns a database handle object.

def Cookbook.connect

return DBI.connect("DBI:Mysql:host=#{@@host_name};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 the list of directories named in its $LOAD_PATH variable (also known as $:), which is an array. To check the default value of this variable on your system, use interactive Ruby to execute this statement:

% irb

>> puts $LOAD_PATH

If you install a library file in one of those directories, your scripts will find it automat‐

ically. If you install the file somewhere else, tell your scripts where to find it by setting the RUBYLIB environment variable, as discussed in the introductory part of this recipe.

2.3. Writing Library Files | 57

After installing the Cookbook.rb library file, try it from a test harness script, harness.rb:

#!/usr/bin/ruby -w

# harness.rb: test harness for Cookbook.rb library require "Cookbook"

begin

dbh = Cookbook.connect print "Connected\n"

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\n"

harness.rb has no require statement for the DBI module. It’s unnecessary because the Cookbook module itself imports DBI; any script that imports Cookbook also gains access to DBI.

If you want a script to die if an error occurs without checking for an exception yourself, write the script body like this:

dbh = Cookbook.connect print "Connected\n"

dbh.disconnect

print "Disconnected\n"

PHP

PHP library files are written like regular PHP scripts. A Cookbook.php file that imple‐

ments a Cookbook class with a connect() method looks like this:

<?php

# Cookbook.php: library file with utility method for connecting to MySQL

# using the PDO module class Cookbook {

public static $host_name = "localhost";

public static $db_name = "cookbook";

public static $user_name = "cbuser";

public static $password = "cbpass";

# Establish a connection to the cookbook database, returning a database # handle. Raise an exception if the connection cannot be established.

# In addition, cause exceptions to be raised for errors.

public static function connect () {

58 | Chapter 2: Writing MySQL-Based Programs

$dsn = "mysql:host=" . self::$host_name . ";dbname=" . self::$db_name;

$dbh = new PDO ($dsn, self::$user_name, self::$password);

$dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

return ($dbh);

}

} # end Cookbook

?>

The connect() routine within the class is declared using the static keyword to make it a class method rather than an instance method. This designates it as directly callable without instantiating an object through which to invoke it.

The newPDO() constructor raises an exception if the connection attempt fails. Following a successful attempt, connect() sets the error-handling mode so that other PDO calls raise exceptions for failure as well. This way, individual calls need not be tested for an error return value.

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 en‐

close all PHP code within those tags. The PHP interpreter makes no 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 include_path variable in the PHP initialization file, as described in the introductory part of this recipe.

PHP scripts often are placed in the document tree of your web serv‐

er, and clients can request them directly. For PHP library files, I recommend that you place them somewhere outside the document tree, especially if (like Cookbook.php) they contain a username and password.

After installing Cookbook.php in one of the include_path directories, try it from a test harness script, harness.php:

<?php

# harness.php: test harness for Cookbook.php library

require_once "Cookbook.php";

try {

$dbh = Cookbook::connect ();

print ("Connected\n");

}

2.3. Writing Library Files | 59

catch (PDOException $e) {

print ("Cannot connect to server\n");

print ("Error code: " . $e->getCode () . "\n");

print ("Error message: " . $e->getMessage () . "\n");

exit (1);

}

$dbh = NULL;

print ("Disconnected\n");

?>

The require_once statement accesses the Cookbook.php file that is required to use the Cookbook class. require_once is one of several PHP file-inclusion statements:

• require and include instruct PHP to read the named file. They are similar, but require terminates the script if the file cannot be found; include produces only a warning.

• require_once and include_once are like require and include except that if the file has already been read, its contents are not processed again. This is useful for avoiding multiple-declaration problems that can easily occur when library files in‐

clude other library files.

Python

Python libraries are written as modules and referenced from scripts using import state‐

ments. To create a method for connecting to MySQL, write a module file, cookbook.py (Python module names should be lowercase):

# cookbook.py: library file with utility method for connecting to MySQL

# using the Connector/Python module

import mysql.connector conn_params = {

"database": "cookbook", "host": "localhost", "user": "cbuser", "password": "cbpass", }

# Establish a connection to the cookbook database, returning a connection

# object. Raise an exception if the connection cannot be established.

# object. Raise an exception if the connection cannot be established.

Dans le document MySQL Cookbook (Page 79-93)