зеркало из https://github.com/mozilla/pjs.git
481 строка
19 KiB
Plaintext
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. |