7. Actions and Models

(what they do and contain (business logic at different levels))

Before we talk about Actions and Models in Agavi let's take a brief look at the MVC (Model-View-Controller) paradigm. The idea behind MVC is that the view (a presentation layer, user interface) and the model (an object, or alike, that represents some information about your application domain) should be de-coupled in such a way that the model is totally independent of the view. In MVC this is achieved by putting a controller between these two (this is not entirely accurate and in any case a simplification but we don't go into that).

When looking at Agavi's classes it's easy to see what's the view part and where's the controller. Models are of course your own classes although they can derive from an AgaviModel class. But where do Actions stand in this scheme? Orthodoxly they are a piece of the controller (or, some could argue, the controller itself) but Agavi's Actions can be seen as the glue between the model and the controller. The Controller decides what Action to trigger and after that it's up to the Action to execute the proper domain logic. Some trivial domain logic can reside in the action but to keep your application in accordance with MVC the actual model part of your design should be separated from the action.

Note

Understanding the idea and the value of MVC is essential in all software designing. For an in-depth rationalization of MVC paradigm you can consult Martin Fowler's Patterns of Enterprise Application Architecture (ISBN 0321127420).

7.1. Models

As said before you write your own models for the application and Agavi doesn't really mind how you do it (some people like to make their models framework independent so that they can be used anywhere). There are, however, a few things to point out that can make it easier for you to use your models.

Firstly you can ask Agavi to handle the loading and the initialization of a model. Agavi divides models into global models and module models. Global models go into app/models/ and module models into app/modules/MyModule/models/. An instance of a model is retrieved using getModel(string modelName [, string moduleName] [, array parameters]) of the AgaviContext. The first, and the only required, parameter of that method is the name of your model class. The second parameter is the name of the module where the model resides. Set/leave it to null to retrieve a global model. If given, the parameter array is passed to the initialization method of you model or to the constructor if initialize(array) is not defined.

Secondly you can make singleton models simply by implementing AgaviISingletonModel. No other changes are needed to your model. Agavi stores the instances of your singleton models. To retrieve an instance, simply call getModel() on the AgaviContext.

Examples:

/* Loads /app/modules/Dogs/models/Dog[Model].class.php and initializes it with a name and a breed. */
$myDog = $this->getContext()->getModel('Dog', 'Dogs', array('name'=>'Nelli', 'breed'=>'Novascotian retriever'));

/* class SinglesBar implements AgaviISingletonModel */
$bar1 = $this->getContext()->getModel('SinglesBar'); //loads and initializes the model.
$bar2 = $this->getContext()->getModel('SinglesBar'); //returns the same instance of SinglesBar as the line above.

7.2. Actions

(actions: execution, execution methods, serving request methods, default view name, validation, security)

Like we noticed before, actions can be seen as a part of the controller, or as glue between the controller and the model. The controller can execute actions in two ways depending on the request method. In a web-context this could mean the controller would call the executeRead -method for a HTTP GET-request or executeWrite for a HTTP POST-request. Both methods take an AgaviRequestDataHolder -object as parameter. If the action contains an execute()-method and neither executeRead() or executeWrite(), the controller will call the execute() method regardless of the request method. If the request method is POST but it does not implement the write method or similarly the request method is GET but the action does not implement the read method, it can run a default view by implementing the getDefaultViewName -method.

Before the controller executes the execute-method, it will check two things: does the action require security (ie a user that has logged in) and what credentials, if any, the user must have to be allowed to execute the action.

The former is simply done by adding an isSecure() -method to the action which returns true if security is required. Credential requirements are defined by implementing a getCredentials() -method which can return either a string indicating a simple credential, or an array of credentials.

To require the credential "Edit", simply return "Edit" from getCredentials(). To require both "Edit" and "Write" -credential, return array("Edit", "Write"). To require EITHER the "Edit" OR the "Write" -credentials, you would return array(array("Edit", "Write")), and to require the "Edit"-credential AND EITHER "Write" or "Foo" (or both), return array("Edit", array("Write", "Foo")).

After the controller has decided if the user is allowed to execute the action, validation occurs. If the validation fails, the action's handleError() will be run, which would, like the execute() return a string or an array indicating which view or action/view -pair should be handed the control. The validation error messages are available for the templates using $container->getValidationManager()->getErrorMessages() which returns an array of messages. For more information on action validation, see section 11 in this manual.

When the action has finished, it must tell the controller which view to execute. This of course depends on the outcome of the action: if we are successful, we would want to pass the control over to a SuccessView, if we had some form of error, we would use ErrorView and if we need input from the user, we'd use InputView. The action can return a string like "Success" to indicate the action finished successfully, and the controller would use the ClassnameSuccessView, or it can return an array with two indices specifying the action/view pair to hand the execution to.