Actions

In PHP every script file may potentially be a receiver of an HTTP request. While this is convenient for simple applications, it is problematic for bigger ones. The reason is that many times the responses perform a similar set of operations, like initialisation, connecting to the database, handle the session etc or check the authorizations. It would be better if there was a standard interface that ensures that each of the responses comply to the same standard. For this reason, Oxygen requires that all responses begin from a single receiver (a controller) which will select one action to perform according to the arguments passed.

Therefore, the basic block of controlling a web application with Oxygen, is the action. An action is essentially a response to an HTTP request. It has to extend the abstract class Action and comply to its interface by overriding the abstract methods. There are two such methods, IsPermitted() and Render(). However, there are many other utility methods that can be overridden to change different properties of an action.

The name of the action is important because this is how the controller will find it. In fact, the controller will check the query string for the value of the parameter action. If for example the query string is ?action=Login, then the controller will select the class with name ActionLogin to handle the request. Therefore, the name of the class has to begin with Action.

Here is a very basic example:

class ActionHelloWorld extends Action { public function IsPermitted(){ return true; } public function Render(){ echo 'Hello World'; } }

To make this work, follow the instructions of the chapter Setup, and place the code into a file named ActionHelloWorld.php into one of the code folders. If your entry script is named index.php, then the action will be accessible from the address index.php?action=HelloWorld. If everything is fine, you will see a Hello World message in the page.

Links

The URL of an action should never be hard coded. It should always be taken from the method GetHref(). Usually, the URL will be exported to HTML, so it will also have to be escaped.

$act = new ActionHelloWorld(); echo '<a href="'.new Html( $act->GetHref() ).'">'.new Html('Hello world').'</a>';

As this is quite common, there are a lot of helping functions. First of all, the Html converter is overloaded so that new Html( $act->GetHref() ) is exactly equivalent to new Html( $act ). In addition, by overriding the helping method GetTitle(), you can keep everything related to the action in one place instead of repeating it everywhere. So, all the variations bellow do exactly the same thing:

$act = new ActionHelloWorld(); echo '<a href="'.new Html( $act ).'">'.new Html('Hello world').'</a>'; echo '<a href="'.new Html( $act ).'">'.new Html($act->GetTitle()).'</a>'; echo $act->GetLinkedTitle();

It becomes evident that the action serves in two different things. If called from the controller (by the query string), it is a handler for the request. If it is called from the code (by the constructor), the action behaves more like a link (although it is still possible to render it). This distinction is very important. First of all, as there are a lot of links in a typical web application, the action object should be lightweight, by avoiding having member variables other than the necessary, which are basically the arguments of the action. In example above, the action needs no arguments as its result depends on nothing. If, however, we wanted an action that shows a page with information on a book, we would want the book to be an argument. Therefore, we would like to pass the book either by the query string or by the constructor. In addition, we would like that the generated URL contained the extra argument. In order to do all these, we have to override three functions of the class (the constructor, a method and a static function). This is a little tricky, because of some peculiarities of PHP, but here it is:

class ActionDisplayBook extends Action { private $book; // // Overriding the constructor. // public function __construct(Book $book){ parent::__construct(); // Always call the parent constructor because PHP will not do so! $this->book = $book; } // // Standard method overriding. This method will have to return a name-value array with the arguments // which will be automatically escaped into URL encoding (escaping an XItem into URL, will actually // escape just its id). // public function GetUrlArgs(){ return array( 'id' => $this->book ) + parent::GetUrlArgs(); // The + operator between arrays, merges the first array into the second. } // // Overriding a static function, with late static binding. This function will be called // by the controller, so it will have to retrieve the arguments from the query string. // public static function Make(){ return new static( Book::Pick( Http::$GET['id']->AsID() ) ); // call of the constructor. } public function IsPermitted(){ return true; } public function Render(){ echo $this->book->Title; ... } }

Now the action is ready to be used, either from a URL or from the code. The generated URL will be something like index.php?action=DisplayBook&id=00000001. On the other hand, to use the action inside the code as a link, simply call the constructor new ActionDisplayBook($book).

Action modes of rendering

The standard mode for an action is to use the entire page for rendering, with the help from the template of the entry script (see the chapter on Setup). This is the default behaviour and it is sufficient for the majority of actions. However, there are other modes:

In the HTML_FRAGMENT mode, the action is rendered bare, without the surrounding template. This is very useful for rendering parts of a page as a response to an AJAX call for example.

In the RAW mode, the action does not even render HTML code. This is useful for those actions where the result of the request will not be displayed as HTML to the user. There are two kinds of such actions. First of all, there are the actions that do not return HTML at all, but other types of content such as binary files, XML, SVG etc. In addition, there are the actions that perform an operation after an AJAX call, but the result of the operation will not be displayed to the user, such as synchronization calls, variable setting calls, etc..

In the AJAX_DIALOG mode, the action is rendered inside a predefined dialog box. This dialog box is modal, which means that the user has to close it before doing anything else on the page. The style of the dialog box can be changed with css.

In the HTML_DOCUMENTK mode, the action is rendered in the whole page but without using the template. In this mode, the action is expected to also render its own HTML header. This is useful for actions rendered inside frames. In addition, in the IFRAME_DIALOG mode, the frame will be placed inside a dialog box.

The mode of an action is determined by the mode protected member variable. The mode of the current action is retrieved by the query string variable mode. So, every action can be rendered in any mode depending on the query string parameter (although it does not always make sense). Each action has a default mode, which is defined by overriding the method GetDefaultMode.

class ActionDeleteBook extends Action { ... public function GetDefaultMode(){ return Action::MODE_AJAX_DIALOG; } ... }

Exceptions

The actions serve also as an exception handler. This means that any exception thrown during the rendering of an action will be caught an displayed to the user in a nice way. There are three kinds of exceptions with a different treatment from the handler.

Any ApplicationException will be caught and the message of the exception will be shown to the user. Therefore, the messages in this kind of exceptions has to be friendly to the user. Furthermore, any SecurityException will be caught, its message will be displayed to the user but along with a login form. Finally, all other kinds of exceptions will display a generic message to the user and an exception report will be send to the developer. So, unless a generic exception is wrapped inside one of the two other kinds, it is considered a bug.

Permissions

The permission system is built on top of the exception handling mechanism. When the IsPermitted method returns false, a SecurityException is thrown. This exception is caught by the system and the login form is displayed.

class ActionDeleteBook extends Action { ... public function IsPermitted(){ return User::GetCurrent()->IsAdministrator(); // for example } ... }

If the user is not an administrator, he cannot delete a book. Instead, the login form will appear if the action is called. Remember that there is no actual user system in Oxygen, so the code in this method always depends on the business rules of the application.

this kind of handling should be used when the action cannot be performed by the current user, but it can be performed by some user. If there is another reason to block the action, then the IsLogical method should be used. This method tells that the action cannot be performed by any user, because it is not logical under the current state of the application. For example, there maybe a business rule that messages can only be sent once. Resending a message will always fail, and it logging in with another user account will not help:

class ActionSendMessage extends Action { ... public function IsLogical(){ return !$this->message->IsSent; // block the resending of a message } ... }

Menus

Menus are arrays of actions. They behave very much like regular PHP arrays, with the exception that they reject any action that is not permitted or is not logical.

$m = new Menu(); $m[] = new ActionEditBook( $book ); $m[] = new ActionDeleteBook( $book ); foreach ($m as $act) echo $act->GetLinkedTitle(); // the menu contains only the actions that are permitted and logical, so no need for extra checking here

The class Menu can be extended to provide specialised menus.

class BookMenu extends Menu { public function __construct(Book $book) { $this[] = new ActionEditBook( $book ); $this[] = new ActionDeleteBook( $book ); } } $book_menu = new BookMenu( $book ); foreach ($book_menu as $act) echo $act->GetLinkedTitle();

Menus also accept a special action called MenuSeparator. This is very useful because the menu will automatically merge two separators in a series and also remove any leading or trailing separator.

class BookMenu extends Menu { public function __construct(Book $book) { $this[] = new ActionViewBook( $book ); $this[] = new MenuSeparator(); // if the user has read-only access to the book, this separator will $this[] = new ActionEditBook( $book ); // be the last item on the list and will be automatically removed. $this[] = new ActionDeleteBook( $book ); } } foreach (new BookMenu($book) as $act ){ if ($act->IsMenuSeparator()) echo '<hr/>'; else echo $act->GetLinkedTitle(); }

From the above examples, it is clear that the IsPermitted and the IsLogical methods are called a lot of times throughout the applications. As a result, they should be executed quickly. If the application uses a complicated permissions mechanism, then this should be optimised so that the results of an authorisation query are returned in constant time.