I have a “list” action which is pretty standard, allowing the user to filter the results using the standard CGridView patterns we’re all familiar with. However I need to be able to output the data in different formats, e.g. JSON/HTML/XML/CSV.
For example:
URL -> Expected output
/accounts/list -> Standard HTML accounts page.
/accounts/list.json -> JSON accounts object. (Used for search suggestions.)
/accounts/list.csv -> CSV accounts file.
The action and data are identical in each case; only presentation differs. (Hence, this is a View issue.)
For reference, my actions() declaration looks like this:
<?php
public function actions()
{
return [
'list' =>[
'class' =>'actions.ViewAction',
'model' =>[$this, 'search'],
'load' =>'GET',
'view' =>'list',
],
...
];
}
(It’s a custom Action class, but it should be clear from the parameters what is going on.)
I don’t want to clutter my actions or controllers with logic for selecting the view. I’m happy to sacrifice a bit of control to simplify the app, especially because this pattern is repeated all over the place. (And not just for lists.)
I decided to use different view files for the different output types, e.g.:
-
list.php = The standard view.
-
list-json.php = The JSON view.
-
list-csv.php = The CSV file view.
-
list-{ext}.php = The general pattern.
I overrode CController::getViewFile() to select a view file based on the requested file extension. (Code below.) Now, given a URL like /accounts.json, my controller checks:
-
Does the view "list-json.php" exist? If yes, render it.
-
Otherwise, render "list.php".
Benefits:
-
Views retain the same basic name ("list"), indicating their relatedness
-
The output type is clearly visible by scanning the views directory
-
No hidden complexity (e.g. ifs / switch statements in the view file)
-
Doesn’t alter standard controller behavior
Has anyone else solved this problem? Is there a more elegant way to handle this?
<?php
class Controller extends CController
{
...
/**
* Searches for view files with a request-specified file extension.
* The extension should be specified in the request object. This searches for
* a view file named using the convention "viewName-ext.php". If the file does not
* exist, it falls back to the normal "viewName.php".
* @param string $viewName name of the view to be rendered
* @return string|false path to the view file, or false if it could not be found
**/
public function getViewFile($viewName, $strict=false)
{
$request = Yii::app()->getRequest();
// Look for a view with the proper file extension
// if one was specified.
if (null !== ($ext = $request->getQuery('ext'))) {
$path = parent::getViewFile($viewName.'-'.$ext);
if (false !== $path) {
return $path;
}
}
// Fall back to the standard view
return parent::getViewFile($viewName);
}
...
}