____ _ ___ _____ 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.