-
Notifications
You must be signed in to change notification settings - Fork 12
Doctrine2 Frontend
Doctrine2 Frontend refers to OSS_Controller_Action_Trait_Doctrine2Frontend
which is a trait that adds CRUD (create, update, delete and list) functionality to a controller for a database object. This is also known as scaffolding.
It's important to note that we build our applications with Zend, Doctrine2, Smarty (and JQuery, Datatables, etc) and so this trait will be tightly coupled with those. This trait also assumes / requires that you have used the Doctrine2
, Smarty
, Message
traits and, if you are copying and pasting view templates from some of our projects, that certain files are available in the view folder. This documentation assumes this.
Firstly, all controllers using this trait as assumed to be enabled unless it has been explicitly disabled in the application.ini
by (where the controller name is SiteController
for example):
frontend.disabled.site = true;
In any controller using this trait, a _feInit()
method is required which configures the controller and, for example, allows you to set what is displayed for different levels of user privileges. The primary purpose of this function is to define the anonymous object _feParams
(using an object ensures that the view gets a reference to the object and not a copy of a static array at a point in time):
protected function _feInit()
{
$this->view->feParams = $this->_feParams = (object)[
'entity' => '\\Entities\\Site', // the ORM entity object that CRUD operations will affect
'form' => 'OSS_Form_User', // the form object to instantiate for add / edit operations
'pagetitle' => 'Users', // page title
'readonly' => false, // default. If true, add / edit / delete will be disabled
'allowedActions' => [], // see Restricting Access section below
'titleSingular' => 'User', // a singular title reference for the object
'nameSingular' => 'a user', // a singular narrative reference for the object
'defaultAction' => 'list', // OPTIONAL; defaults to 'list'
'listColumns' => [ // what columns to display in the list view
'id' => [ 'title' => 'UID', 'display' => false ],
'username' => 'Username',
'email' => 'Email'
],
'listOrderBy' => 'username', // how to order columns
'listOrderByDir' => 'ASC', // direction of order columns
];
// you can then override some of the above for different user privileges (for example)
switch( $this->getUser()->getPrivs() )
{
case \Entities\User::AUTH_SUPERUSER:
$this->_feParams->pagetitle = 'Customer Users';
$this->_feParams->listColumns = [
'id' => [ 'title' => 'UID', 'display' => false ],
'customer' => [
'title' => 'Customer',
'type' => self::$FE_COL_TYPES[ 'HAS_ONE' ],
'controller' => 'customer',
'action' => 'overview',
'idField' => 'custid'
],
'username' => 'Userame',
'email' => 'Email'
];
break;
case \Entities\User::AUTH_CUSTADMIN:
break;
default:
$this->redirectAndEnsureDie( 'error/insufficient-permissions' );
}
// display the same information in the single object view as the list of objects
$this->_feParams->viewColumns = $this->_feParams->listColumns;
}
All the required templates should be in the view folder under frontend/
and named after the action. For example frontend/list.phtml
. You can override this for a specific controller (say the site
controller) by creating a template site/list.phtml
. As you'll see below, frontend allows for a lot of custom content which you should just place in a controller specific folder unless you want it to apply to all frontend controllers.
The list action displays a tabular list of objects from the database.
The list action (at time of writing) is really simple:
public function listAction()
{
$this->listPreamble();
$this->view->data = $this->listGetData();
$this->view->listPreamble = $this->_resolveTemplate( 'list-preamble.phtml' );
$this->view->listPostamble = $this->_resolveTemplate( 'list-postamble.phtml' );
$this->view->listRowMenu = $this->_resolveTemplate( 'list-row-menu.phtml' );
$this->view->listToolbar = $this->_resolveTemplate( 'list-toolbar.phtml' );
$this->view->listScript = $this->_resolveTemplate( 'js/list.js' );
$this->view->listAddonScript = $this->_resolveTemplate( 'js/list-addon.js' );
$this->_display( 'list.phtml' );
}
You can override the listPreamble()
hook to do any pre-list code / tasks.
All you are really required to do is define the function listGetData()
which is used also for viewing a single object (in which case it takes an id parameter). For the list action, this function needs to return an array of rows for display in tabular format. For example (using the site controller):
protected function listGetData( $id = null )
{
$qb = $this->getD2EM()->createQueryBuilder()
->select( 's.id as id, s.name as name, s.shortname as shortname,
c.id as customerId, c.name as customer' )
->from( '\\Entities\\Site', 's' )
->leftJoin( 's.Customer', 'c' );
if( $this->getUser()->getType() == \Entities\User::TYPE_CUSTADMIN )
{
$qb->where( 's.Customer = ?1' )
->setParameter( 1, $this->getUser()->getCustomer() );
}
if( isset( $this->_feParams->listOrderBy ) )
$qb->orderBy( $this->_feParams->listOrderBy, isset( $this->_feParams->listOrderByDir ) ? $this->_feParams->listOrderByDir : 'ASC' );
if( $id !== null )
$qb->andWhere( 's.id = ?2' )->setParameter( 2, $id );
return $qb->getQuery()->getResult();
}
Note the following:
- the above is aware of whether we are doing a list of objects or a view of a single object;
- the name of the columns in the result must match those as defined in
_feInit()
; - you can add logic such as user privilege checking.
The code then looks for common or overridden content to:
- display before the table (
list-preamble.phtml
); - display after the table (
list-postamble.phtml
); - per row tools (such as edit, delete) (
list-row-menu.phtml
); - a toolbar for the page header (
list-toolbar.phtml
); - custom JavaScript to replace the default (
js/list.js
). - additional JavaScript (
js/list-addon.js
).
The view action follows from the list action above and also uses listGetData( $id )
where $id
is the id of the object to display and is typically passed in in the URL such as /$controller/view/id/$id
.
Similar also to the list action, the view action is quite simple but offers some additional hooks:
public function viewAction()
{
$this->viewPrepareData();
$this->view->viewPreamble = $this->_resolveTemplate( 'view-preamble.phtml' );
$this->view->viewPostamble = $this->_resolveTemplate( 'view-postamble.phtml' );
$this->view->viewToolbar = $this->_resolveTemplate( 'view-toolbar.phtml' );
$this->view->viewScript = $this->_resolveTemplate( 'js/view.js' );
$this->_display( 'view.phtml' );
}
The sequence is:
-
viewPrepareData()
can be overridden in your own controller but the default version simply takes the object ID to view from the request ($this->_getParam( 'id' )
) and then callspreView( viewGetData( $id ) )
. -
viewGetData( $id )
can also be overridden but in its default form it callslistGetData( $id )
and ensures there is a result (if not, it redirects to the default page with a not found error). This function must return a object for display. -
preView()
does nothing by default but allows you to override it to mangle the object prior to display if you so desire.
The code then looks for common or overridden content to:
- display before the object view (
view-preamble.phtml
); - display after the object view (
view-postamble.phtml
); - a toolbar for the page header (
view-toolbar.phtml
); - custom JavaScript (
js/view.js
).
The default isn't overly useful except to display more columns than you'd typically display in a table format. The default uses a <dl>..</dl>
format. Defining your own $controller/view.phtml
will allow you to do much more useful things.
The delete action deletes an object from the database. There are a lot of available hooks you can add to add / change functionality and the sequence of events in the deleteAction()
function is:
- First, call
deletePreamble()
to perform any set up tasks - Get the
$id
of the object to delete viadeleteResolveId()
which you can override. This, by default, returns the ID parameter from the request. - Load the object via
loadObject(
$id )` which:- allows a translation / alteration of the
$id
via apreLoadObject( $id )
hook; - load the object from the database (and redirect to default controller action with error if it does not exist);
- allow alteration of the object via a
postLoadObject( $object )
hook.
- allows a translation / alteration of the
- Call a possible
preDelete( $object )
hook which must return true to continue (it should redirect itself to abort the delete); - removes the object from the database and flushes the database;
- calls a possible
postDelete( $object )
hook which should return with true (and which by default calls the more genericpostFlush()
hook); - redirect to the default controller action after logging the deletion and adding a success message unless you override the
deleteDestinationOnSuccess()
function to add a custom message and issue your ownredirect()/redirectAndEnsureDie()
.
This is the more complex of all the actions. Also not that edit is an alias for add and in fact performs an internal _forward()
to addAction()
. The view and hooks will all receive a parameter $isEdit
to indicate if we are editing or adding an object.
Note that this action is also more complex as it requires a Zend_Form
object to add / edit objects.
There are a lot of available hooks you can add to add / change functionality and the sequence of events in the addAction()
function is:
-
- First, call
addPreamble()
to perform any set up tasks
- First, call
- Call the
editResolveId()
function which can be overridden. This is where the add / edit decision is taken. By default,editResolveId()
returns$this->_getParam( 'id', false )
. I.e. if there is anid
parameter, we assume it is the id of an object to edit, otherwise we assume we are adding an object. - If editing, we:
- we call
loadObject( $id )
just like in delete step (2) above (please refer there for the available hooks); - we instantiate a form object by calling
$this->getForm( $isEdit, $object )
- assign the object's existing values to the forms elements.
- we call
- if adding, we instantiate a form via
$this->getForm( $isEdit, $object )
which will use any defined default values. - the
$this->getForm( $isEdit, $object )
call:- instantiates the form object as defined by
_feInit()
; - calls the
formPostProcess( $form, $object, $isEdit, $options = null, $cancelLocation = null )
hook which is a better option to override rather thangetForm()
. By default, this does nothing but this is where you might, for example, remove elements for less privileged users / add elements for more privileged users.
- instantiates the form object as defined by
- the
addPrepare( $form, $object, $isEdit)
hook is now called just before we process a possible POST / submission and here we can change / alter the form or object. - if the form has been submitted(
$this->getRequest()->isPost()
), we:- call the
addPreValidate( $form, $object, $isEdit )
hook which should return true to continue processing the form; - calls the form's
isValid()
mathod and continues if true; - calls the
addProcessForm( $form, $object, $isEdit )
hook which can be overridden but by default:- calls the
addPostValidate( $form, $object, $isEdit )
hook which must return true to continue; - assigns the form's values to the object (NB: ensure there are not form elements named the same as object elements that you do not mean to have assigned);
- calls the
addPreFlush( $form, $object, $isEdit )
hook just before the object is added / changes are persisted to the database which must return true to continue; - calls the
addPostFlush( $form, $object, $isEdit )
hook just after flushing the database which must return true to continue (and which by default calls the more genericpostFlush()
hook); - logs the action and returns;
- calls the
- if
addProcessForm()
returns true, a success message is added to the session and a redirect to the default controller action is issued unless you override theaddDestinationOnSuccess()
function to add a custom message and issue your ownredirect()/redirectAndEnsureDie()
.
- call the
When displaying the add / edit form, some standard templates are checked:
The code then looks for common or overridden content to:
- display before the form (
add-preamble.phtml
); - display after the form (
add-postamble.phtml
); - a toolbar for the page header (
add-toolbar.phtml
); - custom JavaScript (
js/add.js
).
One method of restricting access is shown above via the switch()
statement on the user's permissions.
Another method is to use the xxxPreamble()
action hooks for the defined CRUD functions.
A third method is via the feParams
allowedActions[]
array. If this is not set, then this method does not apply and will be ignored.
If however it is set, then only the actions listed in the array will be allowed. Here is a concrete example:
switch( $this->getUser()->getPrivs() )
{
case \Entities\User::AUTH_SUPERUSER:
// insert list and view column setup...
break;
case \Entities\User::AUTH_CUSTUSER:
$this->_feParams->allowedActions = [ 'configuration', 'export' ];
$this->_feParams->defaultAction = 'configuration';
break;
default:
$this->redirectAndEnsureDie( 'error/insufficient-permissions' );
}
Here, the super use is allowed full access, users with AUTH_CUSTUSER
are allowed access to only two actions: configurationAction
and exportAction
, while any other user is redirected to the error handler.
If the CUSTUSER
tries to access non-allowed actions, they too will be redirected to error/insufficient-permissions
.