I'm using Zend-Framework 1.9.5 to make a web-application, But it's Url_Helper was quite tricky to me in the matter of parameter reset!, I know it's a good feature (parameter preserving) but in most cases I don't need it!.
So I'm thinking of overriding the default Router to force it loosing parameters Unless I ask for it or maybe specifying a certain parameters that it keeps like (lang, or something like that).
Also I want to make it the default router so I don't have to edit my Controllers, Views to get that done!
Any suggestions?
Update:
I spent the whole morning trying to write my url helper Admin_View_Helper_Xurl, But I couldn't do anything that solves the problem:
<?php
class Admin_View_Helper_Xurl extends Zend_View_Helper_Abstract
{
public function xurl(array $urlOptions = array(), $name = 'default', $reset = false, $encode = true)
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$wanted_params = array('module', 'controller', 'action', 'lang', 'page', 'search');
$route = $router->getCurrentRoute();
$something = anyWayToGetThatObjectOrClass();
$params = $something->getParams();
foreach($params as $key => $val) {
if (!in_array($key, $wanted_params)) {
$params[$key] = null; // OR uset($params[$key]);
}
}
$something->clearParams();
$something->setParams($params);
return $router->assemble($urlOptions, $name, $reset, $encode);
}
}
I tried to get current URL parameters and filter them and clear the current parameters and pass my filtered ones but I couldn't do anything that does it without hard-code editing one Zend_Framework code :(.
Thanks
When generating a link a view, you can ask the helper to get rid of all aparamters with a simple boolean :
<?php echo $this->url(array('controller' => 'index', action => 'action'), 'default', true); ?>
The last parameter tells whether to reset parameters or not.
I came up with this solution. It took 7 hours to be functional.
class Zend_View_Helper_Xurl extends Zend_View_Helper_Abstract
{
const RESET_ALL = 'all';
const RESET_CUSTOM = 'normal';
const RESET_NON_MVC = 'mvc';
const RESET_NONE = 'none';
protected $_wantedParams = array('module', 'controller', 'action', 'lang', 'page', 'search');
protected $_router;
/**
* Generates an url given the name of a route.
*
* #access public
*
* #param array $urlOptions Options passed to the assemble method of the Route object.
* #param mixed $name The name of a Route to use. If null it will use the current Route
* #param bool $reset Whether or not to reset the route defaults with those provided
* #return string Url for the link href attribute.
*/
public function __construct()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$this->_router = clone $router;
}
public function xurl(array $urlOptions = array(), $reset = 'mvc', $encode = true)
{
$urlOptions = $this->_getFilteredParams($urlOptions, $reset);
return $this->_router->assemble($urlOptions, $name, true, $encode);
}
protected function _getFilteredParams($data = array(), $level)
{
// $filteredValues = array();
$request = Zend_Controller_Front::getInstance()->getRequest();
$filteredValues = $request->getUserParams();
$$filteredValues['module'] = $request->getModuleName();
$$filteredValues['controller'] = $request->getControllerName();
$$filteredValues['action'] = $request->getActionName();
switch ($level) {
case self::RESET_ALL:
$filteredValues['module'] = null;
$filteredValues['controller'] = null;
$filteredValues['action'] = null;
// break omitted intentionally
case self::RESET_NON_MVC:
$filteredValues['page'] = null;
$filteredValues['lang'] = null;
$filteredValues['search'] = null;
// break omitted intentionally
case self::RESET_CUSTOM:
foreach ($filteredValues as $key=>$val) {
if (!in_array($key, $this->_wantedParams)) {
$filteredValues[$key] = null;
}
}
break;
case self::RESET_NONE:
break;
default:
throw new RuntimeException('Unsuppoted Xurl URL helper reset level.');
break;
}
foreach ($filteredValues as $key => $val) {
if (!array_key_exists($key, $data)) {
$data[$key] = $val;
}
}
return $data;
}
}
Clearly it's a View Helper class, may be not the best solution but it works fine with me for now.
Related
I have a collection called work-monitor where-in I have two fields namely
assignor_remarks and assignee_remarks.
so when a comment is submitted by either assignor or assignee, I want to add those comments in the respective comment filed.
I am able to save the comments in the collection, but new comments is overriding the existing one.
my code is like this:
public function actionWorkUpdate($id)
{
\Yii::$app->request->enableCsrfValidation = false;
$work = $this->modelClass::find()->where(['_id'=>$id])->one();
$work->load(Yii::$app->getRequest()->getBodyParams(), '');
$work->assignee_remarks = ["timestamp"=>date('d-m-Y h:i'),"comments"=>$work->assignee_remarks];
$work->update();
return "success";
}
how I can achieve this.
update like in the example below:
"assignee_remarks":{"comment":"test comment","commentTime":2020-04-29 12.41},
{"comment":"test comment2","commentTime":2020-04-29 12.45},
{"comment":"test comment3","commentTime":2020-04-29 12.50}
Try something like that, if I have understood you correctly.
// In Work Model
public $assignee_remarks;
public function rules()
{
return [
//...
['assignee_remarks', 'safe'] // for free load
];
}
// In controller
/**
* In bodyParams you have new comment like assignee_remarks: 'some text'
* #param $id
* #return mixed
*/
public function actionWorkUpdate($id)
{
\Yii::$app->request->enableCsrfValidation = false;
$work = $this->modelClass::find()->where(['_id' => $id])->one();
$currentComments = $work->assignee_remarks ?? [];
$work->load(Yii::$app->getRequest()->getBodyParams(), '');
$currentComments[] = ["commentTime" => date('d-m-Y h:i'), "comment" => $work->assignee_remarks];
$work->assignee_remarks = $currentComments;
$result = $work->update();
if ($result === false) {
// validation error
} else {
return $result > 0 ? 'success' : 'fail';
}
}
While looking into this question I came up with the following solution that is called from canDelete() in an extension to File:
protected function isFileInUse()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::subclassesFor('DataObject');
$classesWithFileHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
$hasOnes = array_flip($subClass::create()->hasOne());
if (array_key_exists($owner->class, $hasOnes)) {
$classesWithFileHasOne[$subClass] = $hasOnes[$owner->class];
}
}
$threshold = (Director::get_current_page()->class == 'AssetAdmin') ? 1 : 2;
$uses = 0;
foreach ($classesWithFileHasOne as $class => $relation) {
$uses += count($class::get()->filter("{$relation}ID", $this->owner->ID));
if ($uses >= $threshold) {
return true;
}
}
return false;
}
There is one edge case I can't get around though. If, say, a featured image is changed on a blog post then if there is exactly one other use of the same image then with this approach it will still allow it to be deleted. This is because until the page is saved the current change doesn't count towards uses of the image.
The threshold is set differently in CMS Pages and the Media Manager to allow an image to be deleted from within the page that is using it.
Is there a way that I can access the containing page (or other element - we're using Elemental) from within my File extension to see if its associated image has changed?
This is the solution I eventually came up with. I'm not entirely happy with having to inspect the request but couldn't see any other solution:
public function canDelete($member = null)
{
return !$this->isFileInUse();
}
/**
* Check if the file is in use anywhere on the site
* #return bool True if the file is in use
*/
protected function isFileInUse()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::subclassesFor('DataObject');
$classesWithFileHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
$hasOnes = array_flip($subClass::create()->hasOne());
if (array_key_exists($owner->class, $hasOnes)) {
$classesWithFileHasOne[$subClass] = $hasOnes[$owner->class];
}
}
$threshold = ($this->isAssetAdmin() || ($this->isFileAttach($classesWithFileHasOne))) ? 1 : 2;
$uses = 0;
foreach ($classesWithFileHasOne as $class => $relation) {
$uses += count($class::get()->filter("{$relation}ID", $this->owner->ID));
if ($uses >= $threshold) {
return true;
}
}
return false;
}
/**
* Are we in the asset manager rather than editing a Page or Element?
* #return bool
*/
protected function isAssetAdmin()
{
return 'AssetAdmin' === Director::get_current_page()->class;
}
/**
* Is the current action attaching a file to a field that we're interested in?
* #param array $classesWithFileHasOne Classes with a relationship we're interested in and the name of the
* relevant field
* #return bool
*/
protected function isFileAttach($classesWithFileHasOne)
{
$controller = Controller::curr();
$field = $controller->request->allParams()['FieldName'];
return (preg_match('/attach$/', $controller->requestParams['url']) &&
($controller->action == 'EditForm')
&& (in_array($field, array_values($classesWithFileHasOne))));
}
I'm trying to create multilingual application. I've implemented ability of translationable content and next step should be showing it to user. I want to have ability of changing language depending on URL. I've found a couple of components for those purposes but they all create urls which I don't like. For example, my application's default language is English and I have content which is translated into French. I have page "contacts", for instance. And URLs which will be generated by application will be: mysite.com/en/contacts, mysite.com/fr/contacts, but I want to have mysite.com/contacts for default language and mysite.com/fr/contacts for French language. It's simillar for site's root too. mysite.com/ - for default language and mysite.com/fr for French.
Is there any methods for implementing these functionality?
I'm using XUrlManager extension XUrlManager on GitHub
Yii generates URL's based on UrlManager rules. If you want URL's without /lang/ code - you need just create correct rules. For example, if you dublicate records in rules array:
'rules'=>array(
'<_c:\w+>/<_a:\w+>'=>'<_c>/<_a>',
'<language:\w{2}>/<_c:\w+>/<_a:\w+>'=>'<_c>/<_a>',
);
your URL's will be generated withou /en/ and /fr/, but URL's with code works too. By default, XUrlManager use previously selected language and store this in session or cookie.
If you want only hide /en/ and use /fr/ and others always, you can change your XUrlManager extension with:
public function createUrl($route,$params=array(),$ampersand='&')
{
if(!isset($params['language']) && Yii::app()->language!=='en')
$params['language']=Yii::app()->language;
return parent::createUrl($route,$params,$ampersand);
}
I've found very elegant method for solving my problem on http://www.elisdn.ru
Reimplement CHttpRequest
class DLanguageHttpRequest extends CHttpRequest
{
private $_requestUri;
public function getRequestUri()
{
if ($this->_requestUri === null)
$this->_requestUri = DMultilangHelper::processLangInUrl(parent::getRequestUri());
return $this->_requestUri;
}
public function getOriginalUrl()
{
return $this->getOriginalRequestUri();
}
public function getOriginalRequestUri()
{
return DMultilangHelper::addLangToUrl($this->getRequestUri());
}
}
Reimplement CUrlManager
class DLanguageUrlManager extends CUrlManager
{
public function createUrl($route, $params=array(), $ampersand='&')
{
$url = parent::createUrl($route, $params, $ampersand);
return DMultilangHelper::addLangToUrl($url);
}
}
Change config
return array(
'sourceLanguage'=>'en',
'language'=>'ru',
'components'=>array(
'request'=>array(
'class'=>'DLanguageHttpRequest',
...
),
'urlManager'=>array(
'class'=>'DLanguageUrlManager',
...
),
),
...
'params'=>array(
'translatedLanguages'=>array(
'ru'=>'Russian',
'en'=>'English',
'de'=>'Deutsch',
),
'defaultLanguage'=>'ru',
),
);
Create DMultilangHelper
class DMultilangHelper
{
public static function enabled()
{
return count(Yii::app()->params['translatedLanguages']) > 1;
}
public static function suffixList()
{
$list = array();
$enabled = self::enabled();
foreach (Yii::app()->params['translatedLanguages'] as $lang => $name)
{
if ($lang === Yii::app()->params['defaultLanguage']) {
$suffix = '';
$list[$suffix] = $enabled ? $name : '';
} else {
$suffix = '_' . $lang;
$list[$suffix] = $name;
}
}
return $list;
}
public static function processLangInUrl($url)
{
if (self::enabled())
{
$domains = explode('/', ltrim($url, '/'));
$isLangExists = in_array($domains[0], array_keys(Yii::app()->params['translatedLanguages']));
$isDefaultLang = $domains[0] == Yii::app()->params['defaultLanguage'];
if ($isLangExists && !$isDefaultLang)
{
$lang = array_shift($domains);
Yii::app()->setLanguage($lang);
}
$url = '/' . implode('/', $domains);
}
return $url;
}
public static function addLangToUrl($url)
if (self::enabled())
{
$domains = explode('/', ltrim($url, '/'));
$isHasLang = in_array($domains[0], array_keys(Yii::app()->params['translatedLanguages']));
$isDefaultLang = Yii::app()->getLanguage() == Yii::app()->params['defaultLanguage'];
if ($isHasLang && $isDefaultLang)
array_shift($domains);
if (!$isHasLang && !$isDefaultLang)
array_unshift($domains, Yii::app()->getLanguage());
$url = '/' . implode('/', $domains);
}
return $url;
}
}
After all of these steps your application will have URLs which you want
More information here
I have been reading a lot about how and why to use an MVC approach in an application. I have seen and understand examples of a Model, I have seen and understand examples of the View.... but I am STILL kind of fuzzy on the controller. I would really love to see a thorough enough example of a controller(s). (in PHP if possible, but any language will help)
Thank you.
PS: It would also be great if I could see an example of an index.php page, which decides which controller to use and how.
EDIT: I know what the job of the controller is, I just don't really understand how to accomplish this in OOP.
Request example
Put something like this in your index.php:
<?php
// Holds data like $baseUrl etc.
include 'config.php';
$requestUrl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestString = substr($requestUrl, strlen($baseUrl));
$urlParams = explode('/', $requestString);
// TODO: Consider security (see comments)
$controllerName = ucfirst(array_shift($urlParams)).'Controller';
$actionName = strtolower(array_shift($urlParams)).'Action';
// Here you should probably gather the rest as params
// Call the action
$controller = new $controllerName;
$controller->$actionName();
Really basic, but you get the idea... (I also didn't take care of loading the controller class, but I guess that can be done either via autoloading or you know how to do it.)
Simple controller example (controllers/login.php):
<?php
class LoginController
{
function loginAction()
{
$username = $this->request->get('username');
$password = $this->request->get('password');
$this->loadModel('users');
if ($this->users->validate($username, $password))
{
$userData = $this->users->fetch($username);
AuthStorage::save($username, $userData);
$this->redirect('secret_area');
}
else
{
$this->view->message = 'Invalid login';
$this->view->render('error');
}
}
function logoutAction()
{
if (AuthStorage::logged())
{
AuthStorage::remove();
$this->redirect('index');
}
else
{
$this->view->message = 'You are not logged in.';
$this->view->render('error');
}
}
}
As you see, the controller takes care of the "flow" of the application - the so-called application logic. It does not take care about data storage and presentation. It rather gathers all the necessary data (depending on the current request) and assigns it to the view...
Note that this would not work with any framework I know, but I'm sure you know what the functions are supposed to do.
Imagine three screens in a UI, a screen where a user enters some search criteria, a screen where a list of summaries of matching records is displayed and a screen where, once a record is selected it is displayed for editing. There will be some logic relating to the initial search on the lines of
if search criteria are matched by no records
redisplay criteria screen, with message saying "none found"
else if search criteria are matched by exactly one record
display edit screen with chosen record
else (we have lots of records)
display list screen with matching records
Where should that logic go? Not in the view or model surely? Hence this is the job of the controller. The controller would also be responsible for taking the criteria and invoking the Model method for the search.
<?php
class App {
protected static $router;
public static function getRouter() {
return self::$router;
}
public static function run($uri) {
self::$router = new Router($uri);
//get controller class
$controller_class = ucfirst(self::$router->getController()) . 'Controller';
//get method
$controller_method = strtolower((self::$router->getMethodPrefix() != "" ? self::$router->getMethodPrefix() . '_' : '') . self::$router->getAction());
if(method_exists($controller_class, $controller_method)){
$controller_obj = new $controller_class();
$view_path = $controller_obj->$controller_method();
$view_obj = new View($controller_obj->getData(), $view_path);
$content = $view_obj->render();
}else{
throw new Exception("Called method does not exists!");
}
//layout
$route_path = self::getRouter()->getRoute();
$layout = ROOT . '/views/layout/' . $route_path . '.phtml';
$layout_view_obj = new View(compact('content'), $layout);
echo $layout_view_obj->render();
}
public static function redirect($uri){
print("<script>window.location.href='{$uri}'</script>");
exit();
}
}
<?php
class Router {
protected $uri;
protected $controller;
protected $action;
protected $params;
protected $route;
protected $method_prefix;
/**
*
* #return mixed
*/
function getUri() {
return $this->uri;
}
/**
*
* #return mixed
*/
function getController() {
return $this->controller;
}
/**
*
* #return mixed
*/
function getAction() {
return $this->action;
}
/**
*
* #return mixed
*/
function getParams() {
return $this->params;
}
function getRoute() {
return $this->route;
}
function getMethodPrefix() {
return $this->method_prefix;
}
public function __construct($uri) {
$this->uri = urldecode(trim($uri, "/"));
//defaults
$routes = Config::get("routes");
$this->route = Config::get("default_route");
$this->controller = Config::get("default_controller");
$this->action = Config::get("default_action");
$this->method_prefix= isset($routes[$this->route]) ? $routes[$this->route] : '';
//get uri params
$uri_parts = explode("?", $this->uri);
$path = $uri_parts[0];
$path_parts = explode("/", $path);
if(count($path_parts)){
//get route
if(in_array(strtolower(current($path_parts)), array_keys($routes))){
$this->route = strtolower(current($path_parts));
$this->method_prefix = isset($routes[$this->route]) ? $routes[$this->route] : '';
array_shift($path_parts);
}
//get controller
if(current($path_parts)){
$this->controller = strtolower(current($path_parts));
array_shift($path_parts);
}
//get action
if(current($path_parts)){
$this->action = strtolower(current($path_parts));
array_shift($path_parts);
}
//reset is for parameters
//$this->params = $path_parts;
//processing params from url to array
$aParams = array();
if(current($path_parts)){
for($i=0; $i<count($path_parts); $i++){
$aParams[$path_parts[$i]] = isset($path_parts[$i+1]) ? $path_parts[$i+1] : null;
$i++;
}
}
$this->params = (object)$aParams;
}
}
}
Create folder structure
Setup .htaccess & virtual hosts
Create config class to build config array
Controller
Create router class with protected non static, with getters
Create init.php with config include & autoload and include paths (lib, controlelrs,models)
Create config file with routes, default values (route, controllers, action)
Set values in router - defaults
Set uri paths, explode the uri and set route, controller, action, params ,process params.
Create app class to run the application by passing uri - (protected router obj, run func)
Create controller parent class to inherit all other controllers (protected data, model, params - non static)
set data, params in constructor.
Create controller and extend with above parent class and add default method.
Call the controller class and method in run function. method has to be with prefix.
Call the method if exisist
Views
Create a parent view class to generate views. (data, path) with default path, set controller, , render funcs to
return the full tempalte path (non static)
Create render function with ob_start(), ob_get_clean to return and send the content to browser.
Change app class to parse the data to view class. if path is returned, pass to view class too.
Layouts..layout is depend on router. re parse the layout html to view and render
Please check this:
<?php
global $conn;
require_once("../config/database.php");
require_once("../config/model.php");
$conn= new Db;
$event = isset($_GET['event']) ? $_GET['event'] : '';
if ($event == 'save') {
if($conn->insert("employee", $_POST)){
$data = array(
'success' => true,
'message' => 'Saving Successful!',
);
}
echo json_encode($data);
}
if ($event == 'update') {
if($conn->update("employee", $_POST, "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Update Successful!',
);
}
echo json_encode($data);
}
if ($event == 'delete') {
if($conn->delete("employee", "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Delete Successful!',
);
}
echo json_encode($data);
}
if ($event == 'edit') {
$data = $conn->get("select * from employee where id={$_POST['id']};")[0];
echo json_encode($data);
}
?>
I am developing a Facebook app in Zend Framework. In startAction() I am getting the following error:
The URL http://apps.facebook.com/rails_across_europe/turn/move-trains-auto is not valid.
I have included the code for startAction() below. I have also included the code for moveTrainsAutoAction (these are all TurnController actions) I can't find anything wrong with my _redirect() in startAction(). I am using the same redirect in other actions and they execute flawlessly. Would you please review my code and let me know if you find a problem? I appreciate it! Thanks.
public function startAction() {
require_once 'Train.php';
$trainModel = new Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
switch($trainData['type']) {
case 'STANDARD':
default:
$unitMovement = $config->train->standard->unit_movement;
break;
case 'FAST FREIGHT':
$unitMovement = $config->train->fast_freight->unit_movement;
break;
case 'SUPER FREIGHT':
$unitMovement = $config->train->superfreight->unit_movement;
break;
case 'HEAVY FREIGHT':
$unitMovement = $config->train->heavy_freight->unit_movement;
break;
}
$trainRow = array('track_units_remaining' => $unitMovement);
$where = $trainModel->getAdapter()->quoteInto('id = ?', $trainData['id']);
$trainModel->update($trainRow, $where);
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
}
.
.
.
public function moveTrainsAutoAction() {
$log = Zend_Registry::get('log');
$log->debug('moveTrainsAutoAction');
require_once 'Train.php';
$trainModel = new Train();
$userNamespace = new Zend_Session_Namespace('User');
$gameNamespace = new Zend_Session_Namespace('Game');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
$trainRow = $this->_helper->moveTrain($trainData['dest_city_id']);
if(count($trainRow) > 0) {
if($trainRow['status'] == 'ARRIVED') {
// Pass id for last city user selected so we can return user to previous map scroll postion
$this->_redirect($config->url->absolute->fb->canvas . '/turn/unload-cargo?city_id='.$gameNamespace->endTrackCity);
} else if($trainRow['track_units_remaining'] > 0) {
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
} else { /* Turn has ended */
$this->_redirect($config->url->absolute->fb->canvas . '/turn/end');
}
}
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto-error'); //-set-destination-error');
}
As #Jani Hartikainen points out in his comment, there is really no need to URL-encode underscores. Try to redirect with literal underscores and see if that works, since I believe redirect makes some url encoding of its own.
Not really related to your question, but in my opinion you should refactor your code a bit to get rid of the switch-case statements (or at least localize them to a single point):
controllers/TrainController.php
[...]
public function startAction() {
require_once 'Train.php';
$trainTable = new DbTable_Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$train = $trainTable->getTrain($userNamespace->gamePlayerId);
// Add additional operations in your getTrain-method to create subclasses
// for the train
$trainTable->trackStart($train);
$this->_redirect(
$config->url->absolute->fb->canvas . '/turn/move-trains-auto'
);
}
[...]
models/dbTable/Train.php
class DbTable_Train extends Zend_Db_Table_Abstract
{
protected $_tableName = 'Train';
[...]
/**
*
*
* #return Train|false The train of $playerId, or false if the player
* does not yet have a train
*/
public function getTrain($playerId)
{
// Fetch train row
$row = [..];
return $this->trainFromDbRow($row);
}
private function trainFromDbRow(Zend_Db_Table_Row $row)
{
$data = $row->toArray();
$trainType = 'Train_Standard';
switch($row->type) {
case 'FAST FREIGHT':
$trainType = 'Train_Freight_Fast';
break;
case 'SUPER FREIGHT':
$trainType = 'Train_Freight_Super';
break;
case 'HEAVY FREIGHT':
$trainType = 'Train_Freight_Heavy';
break;
}
return new $trainType($data);
}
public function trackStart(Train $train)
{
// Since we have subclasses here, polymorphism will ensure that we
// get the correct speed etc without having to worry about the different
// types of trains.
$trainRow = array('track_units_remaining' => $train->getSpeed());
$where = $trainModel->getAdapter()->quoteInto('id = ?', $train->getId());
$this->update($trainRow, $where);
}
[...]
/models/Train.php
abstract class Train
{
public function __construct(array $data)
{
$this->setValues($data);
}
/**
* Sets multiple values on the model by calling the
* corresponding setter instead of setting the fields
* directly. This allows validation logic etc
* to be contained in the setter-methods.
*/
public function setValues(array $data)
{
foreach($data as $field => $value)
{
$methodName = 'set' . ucfirst($field);
if(method_exists($methodName, $this))
{
$this->$methodName($value);
}
}
}
/**
* Get the id of the train. The id uniquely
* identifies the train.
* #return int
*/
public final function getId ()
{
return $this->id;
}
/**
* #return int The speed of the train / turn
*/
public abstract function getSpeed ();
[..] //More common methods for trains
}
/models/Train/Standard.php
class Train_Standard extends Train
{
public function getSpeed ()
{
return 3;
}
[...]
}
/models/Train/Freight/Super.php
class Train_Freight_Super extends Train
{
public function getSpeed ()
{
return 1;
}
public function getCapacity ()
{
return A_VALUE_MUCH_LARGER_THAN_STANDARD;
}
[...]
}
By default, this will send an HTTP 302 Redirect. Since it is writing headers, if any output is written to the HTTP output, the program will stop sending headers. Try looking at the requests and response inside Firebug.
In other case, try using non default options to the _redirect() method. For example, you can try:
$ropts = { 'exit' => true, 'prependBase' => false };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
There is another interesting option for the _redirect() method, the code option, you can send for example a HTTP 301 Moved Permanently code.
$ropts = { 'exit' => true, 'prependBase' => false, 'code' => 301 };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
I think I may have found the answer. It appears that Facebook does not play nice with redirect, so it is neccessary to use Facebook's 'fb:redirect' FBML. This appears to work:
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender();
echo '<fb:redirect url="' . $config->url->absolute->fb->canvas . '/turn/move-trains-auto"/>';