gecko-dev/webtools/PLIF/Documentation.txt

661 строка
27 KiB
Plaintext

____ _ ___ _____
THE | _ \| | |_ _| ___|
| |_) | | | || |_
| __/| |___ | || _|
|_| |_____|___|_| DOCUMENTATION
[Stylistic Note: The quote character is ". The apostrophe is '.
Functions and methods end with (). Caps are used to indicate a new
term. Variables are quoted, method names are not.]
_______________________
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, coincidentally, 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, called "using get service" or some such.
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,
they just do a straight string comparison with it.
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(6, message). According to the code,
level 6 is "debugging remarks for the section currently under
test". No code in CVS should do anything at level 6 (such as use
the debug() function), it is reserved for personal debugging.
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.
Using these functions also results in a not insignificant performance
penalty. You should think twice before leaving them in tight loops
when checking in.
(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 the reader is introduced to the concept of magic and is then
| walked through the steps of taming the magic for his own purposes.
In Chapter 2, we learnt about services. It turns out that there is
more than one kind of service.
The Plain Old Service - what was described in Chapter 2. There is
one instance of each plain old service. If two parts of the
codebase both ask for a particular service, they get given the same
instance. The same instance of a plain old service is used for the
lifetime of the application.
The Service Instance - an instance of a service created especially
for the requester, and not cached. The lifetime of a service
instance is typically very short.
The Object - an instance of a service that is create by one service
and added to the list of objects. The lifetime of an object is well
defined. All services that request objects providing a particular
service will be given the same instance(s).
Each of these has an associated "get" method on the controller.
getService(name)
Returns the instance of the first service found providing
"name". The service's constructor will be called with one argument,
a reference to the controller, which must not under any
circumstances be held onto.
getServiceInstance(name, arguments)
Returns a new instance of the first service found providing
"name". The constructor will be passed a reference to the
controller followed by the "arguments" parameter.
getObject(name)
Returns the instance of the first registered object found providing
"name" as an object service.
In addition to the first qualifying service or object, one can also
retrieve the list of all qualifying services or objects. The two
methods available to do this are:
getServiceList()
Returns a list containing instances of all the services found
providing "name". The services' constructors will be called with
one argument, a reference to the controller, which must not under
any circumstances be held onto.
getObjectList()
Returns a list containing the instances of all the registered
objects found providing "name" as an object service.
A convenient way of using these lists is through Magic Arrays. Magic
arrays are array references that act as normal objects. The easiest
way to explain these is by example:
my @languages = $app->getCollectingServiceList('example.text')->language;
Pretty simple looking, huh. But that one line does a lot of work.
Let's expand the example to two lines and then study each bit in turn:
my $magicArray = $app->getCollectingServiceList('example.text');
my @languages = $magicArray->language;
The first line returns a Collecting Magic Array of all the services
that are registered and claim to provide the "example.text" service.
This works just like the getService() method, except that instead of
just finding and returning the first match, it finds, instantiates and
returns all the matches (like getServiceList()) wrapped in a
Collecting Magic Array (unlike getServiceList()).
So where's the magic? Well, that's what the second line shows us.
Imagine if you will that the "example.text" service is defined as
having a "language" property that is a single string. If you wanted to
get the list of all the values of the "language" property as returned
by each service in your service list, you would have to use some sort
of "foreach" loop... or, you could just call the method on the magic
array, which then forwards the call to each of the services and
collects the return values into one long list (whence the name).
There are three different types of Magic Arrays.
Collecting - Calls each service in turn, and returns a list
containing the concatenated results of all the calls.
Piping - Calls each service in turn, and returns a list with each
item being a reference to the array which was returned for the
respective service.
Selecting - Calls each service in turn until one returns a defined
value, and returns that value. (Note: always executes the method
calls in an array context, and then returns the first value in a
scalar context.)
There are six methods on the controller ($app) that return magic
arrays. They are:
getCollectingServiceList()
getCollectingObjectList()
getPipingServiceList()
getPipingObjectList()
getSelectingServiceList()
getSelectingObjectList()
The get*ServiceList() methods act just like getServiceList() but
return the appropriate magic array instead of a list, and the
get*ObjectList() methods similarly return the appropriate magic array
primed with what getObjectList() would return.
As you may have noticed in the descriptions of the methods above,
service constructors get passed different arguments depending on
exactly what type of service they are.
Normal services have the lifetime of the $app, and therefore can be
created from any random part of the application. For this reason,
there is no way of choosing a particular set of arguments for the
constructor, and so a simple convention has been picked: the only
argument is the $app reference. (NOTE: services must not hold a
reference to the $app object! If they do, there will be a circular
ownership model and the services and the application will never get
freed. See chapter 7, "Weak References".)
Service Instances, on the other hand, are created on a per-request
basis, and therefore the time of construction is very well defined.
They can be passed particular arguments by passing the relevant
arguments to the getServiceInstance() method.
Objects are inserted into the object list using the addObject()
method, and therefore construction is out of the control of the $app
and so no firm rules can be said about passing arguments to object
constructors.
__________________________________
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 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.
Or not.
___________________________
CHAPTER 9: INPUT AND OUTPUT
| Wherein the magic of $app->input and $app->output is explained.
Some day.
_________________________________
CHAPTER 10: THE SERVICE REFERENCE
| or, what does what do, and how?
You have _got_ to be kidding.
___________________________
CHAPTER 11: COMMON MISTAKES
| Wherein perls of wisdom are given out in order to save the reader
| some time.
If you get an error message of the form:
Can't locate object method "myMethod" via package "a.service.name" at SomePath/SomeModule.pm line 42
...then you are probably calling getService() on the $self object
instead of the $app object. The error happens because
$aPLIFObject->getService('a.service.name')->myMethod();
...results in |$aPLIFObject->{'a.service.name'}| being set to
'a.service.name' and returns that exact value, which then results in
the method call being invoked on the string, as in:
'a.service.name'->myMethod();
...which of course will fail, since the package "a.service.name"
doesn't exist and thus doesn't have a method called myMethod().
For the same reason, this error can happen if you misspell the name of
the service getter method (e.g., if you use |$app->getServiceList()|
instead of |$app->getCollectingServiceList()|).
Another common mistake is tipping the delivery guy $200 for a single
pizza. Ok, it's not a common mistake. However, it is a serious
mistake. Don't do it.
_____________________
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.