pjs/webtools/PLIF/Documentation.txt

481 строка
19 KiB
Plaintext

_______________________
CHAPTER 1: INTRODUCTION
| Wherein the author whines about the people who asked for this
| document and denies all responsability for its upkeep.
People said they wanted documentation.
So.
Here it is.
Documentation.
Not that it's going to be very thorough or anything. Since I change
major parts of my codebase on an hourly basis, and I update the
documentation on an annual basis, this is not going to be of much use
to anyone who expects it to be accurate.
I warn you right now: if you complain about inaccuracies, I'll just
give up any pretense of writing documentation.
______________________
CHAPTER 2: THE CONCEPT
| Wherein services are explained to be the Saviour of the human race
| and an attempt is made to remove some of their mystery.
Services are a key concept to the PLIF architecture. They are the PLIF
version of XPCOM components, DOM interfaces, C++ pure virtual classes
or operating system APIs. They abstract out functionality using Perl's
polymorphism support so as to make consumers implementation-agnostic.
66-----------------------+
| But what does it all |
| _mean_, Austin? |
+-----------------------99
Imagine you want to order Pizza. The typical thing to do is call
Domino's Pizza, place your order, and await the food at your front
door. But what if you're on holiday, and Domino's aren't available in
that area? Your call fails, because you are unable to get Domino's to
ship pizza to you from your home town to your hotel on a different
continent, and thus you starve and die.
Clearly this is suboptimal.
Here is an alternative way of ordering pizza. Instead of picking up
the telephone, you pick up the business directory (aka, the yellow
pages). You look up "pizza takeaway" and search for the first entry
that claims to support deliveries. (I say "claims to support" because
marketing departments are often out of touch with reality.) Next, you
pick up the phone, and dial the appropriate number, without any
attempt to remember this number. You give the details of what you want
delivered. You wait for it to be delivered.
What's the difference, here? Well, there are several. First of all,
you have no idea what business you purchased your food from. Second,
your choice will be affected by the order in which the businesses are
listen in the directory, typically alphabetical, and not by previous
experience, food quality, or prices.
What on earth does this have to do with Perl?
Well, clearly you need to eat Pizza in order to code. Also, it just
struck me that this is in fact a good metaphor for the whole PLIF
thing that someone mentioned earlier. See Table 1.
Real Life | Perl Program
--------------------+-----------------------------------------
Telephone Call | Perl Method Call
Ordering Pizza | Processing Data
Business | A Perl Module
Domino's Pizza | A Specific Perl Module
Pizza | The Method Call Return Value
Front Door | Where The Method Call Returns Its Value
Holiday | Unexpected Environment
Business Directory | A List Of Perl Modules
Deliveries | A Particular Perl Method In A Module
--------------------+-----------------------------------------
Table 1: A mapping of the real life example to the perl program
equivalent, in case the metaphor wasn't blindingly obvious.
Let's be more specific. Say you have a record ID, and you want to get
the data that it refers to out of the database. For simplicity, we
will assume that our database merely associates each number with a
string. So. In the Old World, you would do something like:
SendSQL("select string from data where id = $id");
my $string;
if (@row = FetchSQLData()) {
$string = $row[0];
} else {
$string = '';
}
# do something with $string...
That has some flaws: for example, what happens when you want to change
from SQL queries to QBE queries? What about if the fields in the
database change name?
Instead, what you want to do is delegate the task of querying the
database to some other module, known as a "data source", and merely
concern yourself with getting said data from the data source. To do
this, you first need to get a hold of the data source. The problem is
that you have no idea what data source to use -- do you want the
default SQL database data source or the default database QBE data
source? What about if neither of these exist, but someone will provide
a third type that you don't know about yet?
So instead, you merely ask a central controlling entity -- a registry,
or directory, of all known data sources -- for the data source that
deals with the default database. You then call predefined methods in
the data source. The code would look something like:
my $string = $app->getService('dataSource.default')->getString($app, $id);
# do something with $string...
There are several things to notice here. First of all, to get hold of
the data source we said:
$app->getService('dataSource.default')
That tells us that $app is the controller -- that is to say, the
central registry of all data sources is the main application
object. More on this later. It also tells us that the method used to
get the data source is called "getService".
You may be asking yourself why it is called "getService" instead of
the more obvious "getDataSource".
Well, data sources are not the only thing that you might want to get a
hold of. All the input and output is done using this technique -- so
that the main code doesn't need to know it's talking to IRC or over
HTTP to do its work. More on this later.
The general term for all these different interfaces is "services".
Hence, the name of the method is "getService" -- it gets the
appropriate service. I tried making it more obvious, but it was hard,
so I gave up. There are several other methods that return services,
and they are explained in the chapter describing the workings of the
application object.
You should also notice that getService() gets passed a string -- that
string is used to determine whether or not each registered module
provides the service or not. ("Providing a service" is called
"implementing an interface" in COM terms, I believe.)
The string is generally opaque, although that depends on the
module. What I mean by "opaque" is that modules don't try to parse it
to work out whether or not to claim to support a particular service.
The next thing to notice is that getService() returns an object, and
that it is therefore directly used as such -- the method on the data
source is invoked straight off the return value of the getService()
call, and it is the results of the getString() call on the service
that is stored in $string.
So, in summary: If you want to do something that might be done in
several different ways and the code you are immediately dealing with
doesn't need to know the difference, then you would implement the
'something' as a Service and use the 'getService()' method on the
application object to get a reference to an instance of the service.
Questions raised by this:
1. How do you implement a service?
2. How do you use getService?
3. How do you get an application object?
4. How much should you tip the delivery guy?
We shall cover each of these questions, eventually. First, however,
I'm going to go on a totally different tangent because I am bored with
services now and what to talk about warnings and stuff.
______________________________
CHAPTER 3: PLIF ERROR HANDLING
| Wherein it is first claimed that PLIF has great tools for error
| handling but then that is shown to be totally untrue.
The root of (almost) every PLIF class is the "PLIF" class. What that
means is that at (almost) any point in PLIF-based code, you can use
methods that are part of the core PLIF class. Now, there aren't many
of them, so you'd better make the most of it!
The methods that are of interest to us right now are the following
five debugging aids:
dump(level, message)
Prints the message to standard error. The level argument is a
number, typically in the range of 0-9, stating the verbosity of the
message. Users of your application (as in, the people who install
it, not the people who use it on a daily basis) can change the
debugging level that is printed, so if you have a lot of
dump(9,'verbose debugging information') calls they can easily turn
them off. 0 is the most serious, 9 is the most trivial. Actually,
10 is the most trivial, but 10 is so trivial as to be unusable
unless you are trying to get a headache -- the core PLIF code calls
dump(10,'...') for almost every method call that goes through it.
warn(level, message)
Same as dump(), but includes a stack trace.
error(level, message)
Same as warn(), but raises an exception as well. (You can catch
exceptions using eval{}.)
assert(condition, level, message)
Calls error() if condition is true.
debug(message)
Same as dump(7, message). According to the code, level 7 is
'typical checkpoints (e.g. someone tried to do some output)'.
notImplemented()
Calls error() with predefined arguments.
These tools are a great help. They should prevent you from ever
needing to use print() debugging, for instance. They allow you to
quickly wrap null pointer checks and the like in unobtrusive one
liners while supporting decent amounts of debugging information.
They also allow us to later reimplement the debugging code to add
better support for debuggers or pretty printing or mailing errors to
admins or whatever.
If you've been paying attention you'll notice there were actually six
debugging aids, and not five. This is just to demonstrate that they
will only actually help if you use them, not just if you look at
them. Clearly here I should have been using them if I wanted to catch
that fence-post error.
Unfortunately, using these utility methods to report errors can result
in suboptimal feedback to the user, and so should only be used to
report errors that you really were not expecting, such as missing
configuration files, errors sending mail, failures when connecting to
databases, and so on. For errors in user data, e.g. wrong password,
unknown requests, out of range input and the like, you want to report
the errors using the usual techniques of error codes and callbacks.
(Note. These debugging methods are _class methods_ and therefore you
do not need to ensure that $self is a reference before calling them.)
_________________________________
CHAPTER 4: IMPLEMENTING A SERVICE
| Wherein examples modules are provided on the grounds that they will
| enable the reader to learn how to create modules on their own, but
| with the knowledge that in practice the said examples will only be
| used for the purposes of copy and pasting.
Implementing a service is relatively easy. To demonstrate this, we
shall be implementing a "vendingMachine" service. First, we need to
define what we mean by a "vendingMachine" service, then we need to
define the API, and finally we shall implement it.
Concept Definition. You have to decide when you expect to use the
service -- in this case, it will be called by other parts of the
application when they need some food. The name of the service is
important. In this case, it's just a generic "vendingMachine", but
subtypes could include variants called "vendingMachine.drinks" or
"vendingMachine.sweets", for instance. One example of this in the PLIF
code is all the "dataSource.X" services, which all implement a basic
set of functionality that is used by other parts of the code when they
are passed a data source without knowning what it is.
API Definition. Now, having decided what we think the service is for,
we come to the second step, namely defining the API. This is just as
hard, and in my experience it takes a lot of attempts before you have
one you are happy with.
We're going to say that "vendingMachine" offers these methods:
insertCoins(amount)
Increases the amount of money assumed to be inside the vending
machine. Returns the result amount of cash.
selectSlot(slot)
Decreases the amount of money assumed to be inside the vending
machine and returns a string describing the product that occupied
the slot specified. Returns undef if there was not enough money.
refund()
Returns the amount of money in the machine, and sets it to zero.
Implementation. This is the fun part. Depending on the service, it can
also be the easiest.
I write my Perl modules in Emacs, so first I have a mode line:
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
Next comes the license, in this case MPL/GPL:
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
At last, some code! The first line is the Perl code saying what the
name of the package is:
package VendingMachine::Empty;
Hmm. It appears I've opted for implementing the Empty version of the
service. This ought the be fun. Next comes a bit of standard stuff:
use strict;
use vars qw(@ISA);
That's always there. "use strict" ensures that we avoid the worst of
ugly Perl, and "use vars" is required by the "use strict".
Next, we need to define what module we are inheriting from.
use PLIF::Service;
@ISA = qw(PLIF::Service);
All services must inherit from PLIF::Service or a descendant of that
module (e.g. VendingMachine::Empty!).
1;
This ensures that this module will return true. It's a Perlism.
Ok, finally the real meat. We have to claim that we provide the
vending machine service! This is done using a "provides" method:
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'vendingMachine' or $class->SUPER::provides($service));
}
What this does is return true if the caller asked if we provide a
"vendingMachine" service, and otherwise it defers to the inherited
method. You'll notice this is a class method -- at this point, the
$class variable is probably a class and not necessarily an object.
Next we implement a constructor. (This is actually a method called by
the constructor. Just treat it like a constructor in other languages
and you'll be fine.) We need a constructor because we need to
initialise the amount of money to zero (as opposed to undefined).
sub init {
my $self = shift;
$self->SUPER::init(@_);
my($app) = @_;
$self->money(0);
}
Wowee, lots of PLIFisms there! Let's look at each one in turn. The
first line of the body sets the $self variable to be the reference to
the object. If you are familiar with JavaScript or C++, think "this".
The second line calls the inherited constructor with the same
arguments as was passed to _this_ constructor.
Speaking of which, the arguments are sorted out on the third
line. Most services will be given just one argument on construction,
namely a reference to the application. It is vital that services not
hold on to this! See the Weak References chapter for more details.
Finally, the fourth line is pure fun. Due to some magical fu described
in a later chapter, you can use the syntax shown to set a "field" of
the object to 0. You can also get the value using a call without any
arguments, as in "$self->money". More on this later.
Ok, so now we have to implement the methods that we claim to provide
by saying that we are a vending machine.
# Increases the amount of money assumed to be inside the vending
# machine. Returns the resulting amount of cash.
sub insertCoins {
my $self = shift;
my($money) = @_;
return $self->money($self->money + $money);
}
That method should be self-explanatory... First it sets $self, then it
sorts out the arguments (in this case just one, $money) and then it
uses the syntax described above to add $money to $self->money, which
it returns.
# Decreases the amount of money assumed to be inside the vending
# machine and returns a string describing the product that
# occupied the slot specified. Returns undef if there was not
# enough money.
sub selectSlot {
my $self = shift;
my($slot) = @_;
return undef;
}
The vending machine is empty, right? So that always return undef.
Finally, refund() -- lucky we are going to implement this, otherwise
people could never get their money back!
# Returns the amount of money in the machine, and sets it to zero.
sub refund {
my $self = shift;
my $money = $self->money;
$self->money(0);
return $money;
}
Ok! We have an implementation of a service!
In the next chapter we shall look at how to use it.
.############################## Everything above this line has
#################### BOOK MARK # already been sent to mozilla-webtools
'############################## in some form or another.
____________________________
CHAPTER 5: USING GET SERVICE
| Wherein a family of methods is brought to the front and examined as
| if for a college entrance exam, resulting in the discovery that one
| of the methods is not very bright.
At this point I shall mention that some services get more than just
the $app as an argument on construction...
__________________________________
CHAPTER 6: THE MAGIC OF PROPERTIES
| Wherein it is admitted that the last description of the PLIF class
| was incomplete and was missing some rather important facts.
propertyGet, propertySet, and friends.
__________________________
CHAPTER 7: WEAK REFERENCES
| or, Why The $app Variable Is Passed Religiously From Service To
| Service Without A Thought To Caching It and Why It Would Be Bad To
| Do Otherwise.
You no copy you go boom boom and other stories of the lack of Perl 5.6
on the author's development machine.
______________________________________
CHAPTER 8: THE MAIN APPLICATION OBJECT
| Wherein the reader is introduced to the concept of magic and is then
| walked through the steps of taming the magic for his own purposes.
Or not.
___________________________
CHAPTER 9: INPUT AND OUTPUT
| Wherein the magic of $app->input and $app->output is explained.
Some day.
_____________________
CHAPTER n: CONCLUSION
| Wherein it is revealed that all is subject to change, only available
| while stocks last, and void where prohibited by law.
The End.