The purpose of this tutorial is to instruct you on how to setup your first Studs-based application. Studs is a complete port of Apache's Jakarta Struts to PHP and is supported by an entire container framework emulating the Java Servlet specification. Therefore, knowledge of Java Web Applications will prove to be especially helpful. The lifecycle of Studs parallels that of Java and Servlets within reason (meaning within the limitations of PHP). Therefore, studying JSP and Java Servlets will help you understand the way that the Studs MVC Framework+ functions and how it is organized. However, this tutorial should provide the necessary instructions to allow any web developer familiar with PHP to get started using Studs to create a web-based project.
To get started, the first requirement is to download the source code for the Studs MVC Framework+. The project home page is located at http://www.mojavelinux.com/projects/studs and is dually registered on SourceForge under the project name "studs". While the entire source tree is available through CVS or as a tarball in the "Files" section of the SourceForge project page, it is necessary to first organize the files into a webapp structure. Thus, the quickest way to begin developing with Studs is to grab the preconfigure package named studs-basic, a bare-bones skeleton project all ready for deployment.
As previously mentioned, there is a package available on the Studs project page named studs-basic. This package contains the proper webapp directory structure, all of the configuration files (struts-config.xml, web.xml), the tag library descriptor files (.tld files), and the lib directory containing all of the PHP class files that you will need to get Studs up and running.
To install the basic application, first select a folder anywhere in the web server tree (assuming of course that PHP 4 is available). This folder will be referred to as the "context" folder. Extract the compressed package into that location, renaming the root folder as needed. When this step is complete, you should have a directory structure as follows (assuming you used the default root folder name of studs-basic
):
studs-basic/ studs-basic/index.php studs-basic/index.psp studs-basic/.htaccess studs-basic/pages/ studs-basic/pages/welcome.psp studs-basic/WEB-INF/ studs-basic/WEB-INF/web.xml studs-basic/WEB-INF/struts-config.xml studs-basic/WEB-INF/lib/ studs-basic/WEB-INF/lib/${sources} studs-basic/WEB-INF/classes/ studs-basic/WEB-INF/classes/application.properties studs-basic/WEB-INF/tld studs-basic/WEB-INF/tld/phase-core.tld studs-basic/WEB-INF/tld/studs-html.tld studs-basic/WEB-INF/work
For anyone who has developed an application in Struts, this hierarchy should look strikingly familiar. Studs, just like Java Servlets, uses a special protected directory named WEB-INF/
, which hosts configuration artifacts, libraries and source code. None of the files underneath this directory are accessible to the outside world unless they are specifically included by a page which is accessible (the .htaccess file shipped with Studs is responsible for hiding these files).
The most important configuration detail to check is that the folder inside the /WEB-INF/
directory named work
is made writable by the web server. The /WEB-INF/work/
directory stores the cached ServletContext (application-scoped variables) as well as each compiled PHP Server Pages file.
An error will be displayed on the screen in the event that the /WEB-INF/work/
directory is not writable.
Unlike Java Servlets, PHP's environment can be describe as kaleidoscopic (many possible configurations). Part of this uncertainty comes from the fact that PHP is strongly coupled with the Apache Web Server configuration and, on the other hand, thanks to varying opinions of how the php.ini
file should be setup. To bridge this gap, the .htaccess
override file is used to limit access to a single controller file (index.php
) and to override some of PHP's initialization settings.
error_reporting 2047 display_errors 1 register_globals 0 magic_quotes_gpc 0 html_errors 0 allow_call_time_pass_reference 1
In some cases, the Apache Web Server can be set to ignore extra path info. The implicit variable $_SERVER['PATH_INFO']
includes everything after the script but before the occurance of the query string delimiter, "?". It is possible to test the web server's configuration by creating a phpinfo() file and requesting it as test.php/foobar?key=value. In this case, the PATH_INFO is /foobar. If it is empty, path info must be enabled, which can be done by adding the flag AcceptPathInfo On
in the .htaccess
file.
Studs is based on the MVC model, which divides the responsibility of serving an application into distinct units of work. Each request is routed through a single controller, which delegates out this work. While Studs behaves like Jakarta Struts, its implementation is drastically different. A J2EE web container acts as both a web and application server, handling all the low level details of the HTTP transaction. The servlets that the J2EE web container initializes are long running, stopping only when the server stops or is restarted. (Studs caches the ServletContext to a serialized file to emulate the long running application context between page requests).
One of PHP's greatest asset is its ability to delegate, in this case to the Apache Web Server. PHP relies on the web server to setup the HTTP environment, as well as handle both the request and the response. Each request to the Studs web application goes through a single controller file, index.php
, which bootstraps the Stratus Servlet Container and dispatches to the appropriate servlet class based on the extra path info in the URL (the part after index.php). The .htaccess
file contains important initialization settings for PHP and security restrictions that are a necessary part of the Studs MVC Framework+ environment. Both the index.php
and .htaccess
files must be located at the root of the context (the folder where Studs is installed). Since the index.php file must be used on every request, the URL will always contain this script name. The two controller files work together to setup and dispatch the HTTP request to the ActionServlet (the servlet instance for the Studs application).
Also present in the /WEB-INF/
folder are the lib/
directory and the classes/
directory, both of which are put in PHP's "path". The lib/
directory holds the entire source tree of the Studs MVC Framework+ library, while the classes/
directory hosts custom classes specific to the web application.
An action defines an entry point into your application, responding to a browser request and mapping a URL path to a PHP class. All actions subclass the studs.action.Action class and are invoked by the ActionServlet on each request. The action can either write directly to the output stream by using print() function, or it can return an ActionForward. When an ActionForward is returned, the ActionServlet delegates the output to its associated "view" file, typically a PHP Server Page (PSP).
Before continuing on the topic of the action, it is necessary to understand the resources available to the execute() method that will be called. The configuration file for Studs is struts-config.xml, which is located in the /WEB-INF/
directory. The struts-config.xml file is configured in exactly the same way as it is in Jakarta Struts. The only difference is that the configuration values point to resources within the Studs Framework. Creating an action in Studs differs very little from creating an action in Struts (though some language differences are important to note). In order to map a URL request to an Action class, a new <action> node should be created as a child of the <action-mapping> element in the struts-config.xml file. An example node is provided below.
<action path="/firstAction" type="webapp.FirstAction" />
The next step is to create an Action class to process this request. The execute() method of this class will be invoked by the ActionServlet when the request matches the path defined above. An example class is provided below.
<?php // package webapp import('studs.action.Action'); class FirstAction extends Action { function &execute(&$mapping, &$form, &$request, &$response) { $request->setAttribute("message", ref("This is your first action. You should be very happy!")); return $mapping->findForward("success"); } } ?>
It is important to mention those funny & characters in front of the variables. Any time an object is sent to a function or method in PHP, it is first copied. This copy operation can be completely disastrous in an object-oriented programming environment since most of the time it is necessary to continue working with only a single copy of the object. Those & signs instruct the intepreter to carry the same version of the object into the function or method. To avoid problems down the road, always use the & as shown in the code above when passing around objects, unless you know you specifically want to work with a copy. Additionally, it is necessary to "wrap" raw scaler values, such as integers and strings, in the special wrap() method to get around a reference handling parse error in PHP. The other option would be to create a temporary variable.
The only step remaining is to wire the forward named "success" to a view page by first creating a target page and then adding the definition to the struts-config.xml file. It is good practice to create a folder to hold all of your "views" (or "forwards") at the root of the directory structure named "pages". The view pages are typically PHP Server Pages, which are just a HTML pages with special preprocessed instructions, including PHP scriptlets, custom tag libraries and/or JSTL-like shorthand. The PSP page for our Action, located at /pages/welcome.psp
, is show below:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Studs QuickStart Tutorial</title> </head> <body> <h3>Studs QuickStart Tutorial</h3> <p>${message}</p> </body> </html>
Wiring the "success" forward to this PSP page is done by adding a <forward> node as a child of either the <global-forwards> element or as a child of the <action> element you just created in the struts-config.xml file.
<forward name="success" path="/pages/welcome.psp" />
At this point, whenever the web application configured at this context receives a request with /firstAction.do at the end (ie. http://localhost/studs-basic/index.php/firstAction.do), the execute() method of the FirstAction class will be invoked. It is the web.xml file that is mapping the *.do url pattern to the Studs ActionServlet, which in turn matches the /firstAction path to the Action class. The portion of web.xml that configures the Studs ActionServlet is listed below:
<servlet> <servlet-name>action</servlet-name> <servlet-class>studs.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
Often times, when working with a Studs project, it is necessary to "restart" the context. Although PHP Server Pages have to be recompiled each time they are changed, the Phase Engine automatically detects when a PSP file has been updated an recompiles it appropriately. Additionally, since PHP is a language which is intepretted at runtime, any changes to any source code in the /WEB-INF/classes/
directory will be effective immediately (such as a change to the FirstAction class). This obviously makes working in a testing environment extremely quick and easy.
However, at some point it is necessary to actually clear the application scope. Whenever changes are made to the struts-config.xml
file or the web.xml
file, it is necessary to "reload" the context, or otherwise flush the cache of the current configuration. This process is done by removing the file located at /WEB-INF/work/ServletContext.ser
(until something more graceful is available). Not only does this file keep a serialized version of the application-scope environment, it also caches instances of the servlets so that it is only necessary to invoke the doService()
method after the first run.
Studs was developed for two main reasons. One, to allow Java programmers to have a familiar environment when working in PHP and second, to allow PHP programmers to dive into the world of Java without leaving their hometown. Any resource on the web discussing Jakarta Struts could prove useful to developing applications with Studs, such as the one available on the Struts Live webpage written by ArcMind, Inc. Remember, Studs is an open source program, so if you have an idea for how to make it better, jump on the team and start hacking away. Happy building!