composer
install
to finish! (And hit GitHub download quotas less
often!)is a small PHP library that provides [very lightweight] abstractions for:
What this gives us is the ability to write web request handlers in a very functional way, minimizing the amount of procedural (global state-manipulating) code.
PHPProjectInitializer can be used to set up a new Nife-based project with minimal effort on your part.
The Nife-based framework provides a pattern to follow in order to keep your code concise and flexible. A description of said pattern follows.
At the top level of the project there is some procedural
'bootstrapping' code. This includes www/bootstrap.php
[example],
which kicks off processing of incoming requests (either from Apache or
the PHP's built-in webserver; you might want to write alternate
bootstrap scripts for different servers), which
includes init-www-error-handling.php
and init-environment.php
. These set up any global state
needed by your program (error handlers, autoloaders, fixing bad PHP
settings) and create a 'registry' object.
Following their inclusion, bootstrap.php asks the registry for a
'dispatcher'. The dispatcher is asked to
handle the request, returning a response,
which is then output using Nife_Util::outputResponse
.
To put in list form, the basic process is:
www/bootstrap.php
is a reasonable place to put minor
customizations to this process (such as logging errored requests).
gets passed all over the application (usually as a constructor argument). Its responsibilities are:
This is conventionally done with a getConfig( $config_key
)
method and __get
method which must be clever
enough to find (and instantiate, and probably cache instances of)
component classes based on the name of the requested property. If a
project's namespace is "Fred_CoolApp
", its registry class
will be called "Fred_CoolApp_Registry
", and
"$registry->frobber
" would be reasonably expected to
return some instance of Fred_CoolApp_Frobber
. (I'm using
underscore-delimited namespaces in these examples because I think
they're easier to deal with; your project can just as well use
backslashes and do the full PHP 5.3 namespace thing).
Note that, as with all components, it's possible to have multiple registry objects. If you've never needed an alternate 'global state' this probably doesn't matter to you. If you have, then you're probably well-aware of how difficult other frameworks make this.
decodes request parameters, decides what action to take, does it,
and returns a Nife_HTTP_Response
. This is the point
where all the actual work gets done, and may be separated further:
Exactly how that is done is completely up to the application, but a conventional approach is to simply write a long if-else chain for mapping routes to actions.
are an abstraction on the idea of 'something to be done'. They can be thought of as zero-argument functions that may have important side-effects. If you're a Haskeller you might think of IO monads and what I'm calling 'actions' as pretty much the same concept.
Within a Nife-based project, you might define
a PageAction
class for each different page / form
submission handler ("PageAction
" being the conventional
superclass or interface name for actions that return HTTP responses).
This naturally breaks up your application into nicely-sized,
independently-maintainable units. You might have action classes
like Fred_CoolApp_PageAction_ShowHomePage
(which would be
instantiated in response to GET /
)
and Fred_CoolApp_PageAction_SubmitCoolForm
(presumably
instantiated in reponse to POST /cool-form
or something
similar). You can, of course, namespace your action classes further
if you have a lot of them and there is an obvious organizational
scheme. In some cases you may want to build actions programatically,
as opposed to defining a new class for each one. The logic for when
and how to do so would go in your Dispatcher class alongside that for
the class-based actions.
Having a class for each action also has the advantage that you can
define additional methods on them. You probably want to
leave __invoke()
to mean 'unconditionally try to do the
action, either returning a Nife_HTTP_Response
or throwing
some exception', but maybe you want to determine whether the action is
allowed separately from doing the action. In that case you could
define an isAllowed($user)
method on them, and have your
dispatcher automatically return an error when that returns false
instead of invoking the action. In other cases it may make more sense
to have a separate permission-checker object determine whether actions
are allowed based on other metadata. Either way, action objects give
you the flexibility to de-couple the code that reasons about actions
from the code that implements their invocation.