views:

1387

answers:

3

I am developing a Facebook app in Zend Framework. In startAction() I am getting the following error:

The URL http://apps.facebook.com/rails%5Facross%5Feurope/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');
  }
A: 

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);
daniel
Thanks for the suggestions Daniel. I tried them, but the error remains. Still, I appreciate the help.
Chris Barnhill
+2  A: 

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;
    }
    [...]
}
PatrikAkerstrand
I agree: that code does require refactoring. It's ugly and unmanageable. The data should be moved from application.ini to a db table. Your suggested code is very impressive, but possibly a bit overkill for such a simple application :) Thanks for going to all that effort though!
Chris Barnhill
A: 

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"/>';
Chris Barnhill