-
Notifications
You must be signed in to change notification settings - Fork 40
Writing and Upgrading Modules
This specification is currently in flux and subject to change at any time. Please do use this as guidance to upgrade old modules or create new modules, but be aware that there may be some API changes before we call it done.
At Appalachian State University, we use phpWebSite in many different ways - for websites of course, and for rapid development of quick little one-off modules. We also use it as the foundation for several large web applications. We have learned a lot since phpWebSite 1.x and have made significant changes to how modules work in 2.x. The goal is for phpWebSite 2.x to stay out of your way so that you can quickly create simple modules to satisfy the specific and unique needs of your users, and also to provide some of the interfaces that we have found are helpful in deploying large-scale enterprise web applications.
Please note that all paths in this document are relative to the root of your phpWebSite installation. This will be familiar for existing phpWebSite users. However, we are working towards a system that is not so dependent on the relative locations of files on the filesystem, and so if you are working on a more advanced phpWebSite deployment, this document may be more helpful if you think of it in terms of how you would set up just a single website - that is, untar the tarball, install, don't edit any configuration to change any paths. You can do that sort of thing after your module is written, and as long as your module uses library functions to get the filesystem locations you need, it will just work on any deployment scenario.
There are some specific "bad habits" in 1.x that we want to make sure to address in 2.x:
-
Routing - Each individual resource in the system will be accessible directly by a unique URL. The URL is sliced up by the directory separator (
/), and each token or group of tokens are used to address aController. No two URLs point to the same object, and redirections should be handled by a specific URL eventually resolving to aRedirectResponse. Additionally, given a controller, you should be able to determine the URL needed to access it to provide links and form targets. -
Query String - While we cannot prevent use of a query string, as there are very legitimate uses for the same, today's Internet is not friendly to accessing and sharing content in this way. Query strings are to be reserved for performing the original intended purpose of their namesake - performing queries. For instance, a list of every page in the system should be available at
GET /page; but the reality is that in most cases, you need more functionality than just a list. So, you can use the query string to alter how the server returns data from this URL; for instance, the pager itself will limit how many pages are requested like so:GET /page?begin=50&limit=25, or if you want to search the set of all pages:GET /page?search=search+term, or if you want to filter by tag:GET /page?tags=tag1,tag2,tag3Note: mod_rewrite or some other mechanism for rewriting URLS (including the index.php/path/to/object convention in PHP) are now required by phpWebSite. We will support the index.php?module=mymod syntax up until version 2.0, but at that point you will need to have upgraded your modules to support proper URLs. -
AJAX (Echo And Exit) - In past, if you wanted to use AJAX or otherwise wanted to skip Layout, your only option was to manually output response data and then terminate from php. This means that many objects are loaded, initialized, and never used on these types of requests; and the guaranteed functionality of calling close.php at the end of execution is not actually guaranteed. Through the
Responsemechanism, you can tell phpWebSite how to handle the data you are returning. In most cases, a simpleResponsewith an HTMLViewwill suffice. Additionally, the HTTP Response Content-type will be determined by the HTTP Accept string, and the correct view will be dynamically loaded for you. This means that your responsibility is simply to provide data to a genericViewobject, and provide the template for HTMLViews, and if the object is requested in an AJAX fashion, phpWebSite will skip templating altogether and properly return the JSON object to the client. -
Switch Statements - In an of themselves, not a bad thing, and actually we are not stopping you from using them. If you prefer the old way of writing phpWebSite modules, all you really have to do is create a
Moduleobject and begin procedural execution from theexecutefunction. However, we strongly encourage you instead to implementgetControlleron yourModule, do not overrideexecute, and use thegetCurrentTokeninRequestto determine which controller should be used in your module. -
Respect for HTTP - If you want to really understand how the new Module system works, read the HTTP specification, particular the sections on Request and Response. We will be relying more heavily on proper, standardized in HTTP Requests and Responses. For instance, the type of view that will be shown depends entirely on the HTTP Request
Acceptheader. AJAX is much easier to do in phpWebSite 2.x, as much of the needed functionality for returning proper HTTP Status Codes and accessing, creating, and deleting your objects through proper HTTP Request Methods are built into the Module and Controller APIs.
A web request into phpWebSite 2.x will work like this:
-
Load
inc/Bootstrap.php- Registers the phpWebSite autoloader and loads database configuration -
Instantiate
PhpwebsiteController-PhpwebsiteControlleris aControllerjust like any other, except that it is responsible for routing requests to modules. -
Get the Current
RequestObject - There is always a "currentRequest" available from the GlobalServerSingleton. You can also pass modified requests around in the system and indeed this is how to do an "internal request" to get data from another module. However, the HTTP Request from the web server's point of view is always available from theServersingleton. -
ExecutePhpwebsiteControllerwith CurrentRequestObject - Gets phpWebSite running. -
Load
ModuleObjects - TheModuleobject is how your module interfaces with phpWebSite. The functionality that was provided bymod/yourmod/inc/init.php,mod/yourmod/inc/runtime.php,mod/yourmod/inc/close.php, andmod/yourmod/index.phpare now all rolled up into Module.php. -
For each
Module, callModule::init()- Analogous tomod/yourmod/inc/init.php, this is run before the session is loaded to perform any initialization tasks. -
Load Active
Module- Uses the first URL token to determine which module to load for primary content. -
For each
Module, pass ActiveModuleand CurrentRequestinto beforeExecute() - The module that is about to be run and the Request object will be passed around to everyone before execution takes place. Additionally, you can change theRequestobject at this point. This is how user authentication will be implemented in 2.x. We leave it to your imagination to come up with what other useful things could happen here. -
Execute Active
Module- This is the entry point to your code. If you are using the extra functionality provided byModule, at this point phpWebSite will take the controller you provide and execute it. Any further routing should happen within your module, and we strongly recommend usingRequest::getNextToken()and potentially multiple sub-Controllers to control program flow. -
Your module either throws an
Exceptionor returns aResponse- Control should ALWAYS be returned CLEANLY to phpWebSite. Exceptions should only be used for truly exceptional circumstances, in which you simply cannot recover from a failure. Responses will probably be more frequently used. For instance - if you are usingRequestto process URL Tokens and you come across a token your module did not expect, what actually happened is the user entered an address that does not exist. In this case, you should properly return aHttp\NotFoundResponsefrom your code and let phpWebSite handle it. On the other hand, if a SOAP request failed and your module cannot continue without SOAP data, it would be appropriate to throw aHttp\InternalServerErrorcontaining further debugging information. If you do this, you will not only ensure that all errors follow a consistent user interface, but also that phpWebSite can output your errors as JSON instead of HTML if that's what the client requested. -
For each
Module, pass ActiveModuleand CurrentRequest, and theResponsefrom step 10 into afterExecute() - At this point, the request has happened and the module has returned its result. Now, every module has a chance to look at what was passed in and what will be returned. This is where Logging will happen in 2.x, and we leave it to your imagination to think up what other great things could be done at this point. -
The
Responseis Rendered - For all requested response types other than text/html, at this point phpWebSite basically sets headers, echoes, and exits. If the response type is text/html, control is passed to the (yet to be written) Layout management system, which will make further Requests into phpWebSite for other modules that may be decorating your page.
Note: There are some cases where it is useful to return text/html from the server but NOT pass it through Layout, for example to get the body of a jQuery Dialog popup. We cannot determine this based on the HTTP Accept parameter. However, if the request was made through a system that sets the Request header X-Requested-With: XMLHttpRequest (basically everything does this), the response will NOT be passed through Layout and will simply be echoed out.
For this example, I will upgrade the Skeleton module to be completely compatible with phpWebSite 2.0.
- Diagram of Control Flow
- Library Access Mechanism