Calling a PHP class method using AJAX - php

I have come up with the following bits of code to call a method via AJAX in my PHP classes:
PHP:
class Ajax extends Controller {
private $class;
private $method;
private $params;
function __construct()
{
$this->params = $_POST; // Call params
$call = explode('->', $this->params['call']);
$this->class = new $call[0]; // e.g. controller->method
$this->method = $call[1];
array_shift($this->params);
$this->parse();
}
public function index()
{
//Dummy
}
public function parse()
{
$r = '';
$r = call_user_func_array(array($this->class, $this->method), $this->params);
echo $r;
}
}
Client:
function creditCheck2(id)
{
$.post(ROOT + 'Ajax', {call: 'Record->creditState', id: id, enquiryid: enquiryId}, function(data) {
alert(data)
}, 'json')
}
It seems to work great, but is it secure and could it be better?
Just for reference, I have added my code with the changes suggested by the answers:
class Call extends Controller {
private $class;
private $method;
private $params;
private $authClasses = array(
'Gallery'
);
function __construct()
{
$this->params = $_POST; // Call params
$call = explode('->', $this->params['call']);
if(!in_array($call[0], $this->authClasses))
{
die();
}
$this->class = new $call[0]; // e.g. controller->method
$this->method = $call[1];
unset($this->params['call']);
$this->parse();
}
public function parse()
{
$r = '';
$param = array();
// Params in any order...
$mRef = new ReflectionMethod($this->class, $this->method);
foreach($mRef->getParameters() as $p) {
$param[$p->name] = $this->params[$p->name];
}
$this->params = $param;
if($r = #call_user_func_array(array($this->class, $this->method), $this->params))
{
echo $r;
}
else {
}
}
}

Small issues
It could be better in that array_shift($this->params) unnecessarily assumes that the first item in the params array will always be call. That's not true and it does not agree with the direct access $this->params['call'] you are doing a little earlier. The array_shift should be replaced with simply unset($this->params['call']).
Bigger issues
There is also the problem that the order of values in the params array must match the order of parameters in the signature of the method you are trying to call. I don't think there is a guarantee that the order will be the same as the order of the parameters in the AJAX request, so that's a theoretical problem.
VERY big problem
More importantly, this way of doing things forces the author of the AJAX code to match the order of parameters in the signature of the method you are trying to call. This introduces a horrible level of coupling and is a major problem. What's worse, changing the order of the parameters by mistake will not be apparent. Consider:
public function bankTransfer($fromAccount, $toAccount, $amount);
$.post(ROOT + 'Ajax', {
call: 'Bank->bankTransfer',
from: "sender",
to: "recipient",
amount: 42
}, function(data) { ... });
This would work. But if you do this
$.post(ROOT + 'Ajax', {
call: 'Bank->bankTransfer',
to: "recipient", // swapped the order of
from: "sender", // these two lines
amount: 42
}, function(data) { ... });
You will get the opposite result of what is expected. I believe it's immediately obvious that this is extremely bad.
To solve the problem you would have to use reflection to match the array keys in $this->params with the formal names of the parameters of the method being called.
Security
Finally, this code is insecure in that anyone can make a request that directs your code to call any method of any class with the appropriate parameters -- even methods that should not be accessible from a web environment.
This is another serious problem and cannot really be fixed unless you introduce some type of filtering to the dispatch logic.

It seems to work great, but is it secure and could it be better?
Are you using your own framework or using other framework? I believe that it isn't secure at all, if the attacker know what might be inside your framework. For example: there is database class in your framework, attacker can do the following:
{call: 'Database->execute', sql: 'SELECT * FROM information_schema.`tables`'}
Filtering
You can limit the number of class that you allow user to access. For example:
if (!in_array($this->class, array('Record', 'Hello'))) {
die();
}
Reflection
This is sample of reflection that I just learn (Thanks to #Jon for the reference). This solve the problem of passing argument in different order from the PHP function.
class Email
{
public function send($from, $to, $msg) {
return "Send $from to $to: $msg";
}
}
$rawParam = array('msg' => 'Hello World',
'to' => 'to#gmail.com',
'from' => 'from#gmail.com');
$param = array();
// Rearrange
$methodRef = new ReflectionMethod('Email', 'send');
foreach($methodRef->getParameters() as $p) {
$param[$p->name] = $rawParam[$p->name];
}
var_dump($rawParam);
var_dump($param);

Related

Cookie check in OOP

Until yesterday I was burning my brain trying to switch from a procedural thinking to a OOP thinking; this morning I gave up. I said to my self I wasn't probably ready yet to understand it.
I started then coding in the usual way, writing a function to check if there's the cookie "logged" or not
function chkCookieLogin() {
if(isset($_COOKIE["logged"])) {
$logged = 'true';
$cookieValue = $_COOKIE["logged"];
return $logged;
return $cookieValue;
}
else {
$logged = 'false';
return $logged;
}
}
$result = chkCookieLogin();
if($result == 'true'){
echo $cookieValue;
}
else {
echo 'NO COOKIE';
}
since I run across a problem: I wanted to return two variables ($logged and $cookieValue) instead of just one. I google it and I found this answer where Jasper explains a method using an OOP point of view (or this is what I can see).
That answer opened me a new vision on the OOP so I tried to rewrite what I was trying to achieve this way:
class chkCookie {
public $logged;
public $cookieValue;
public function __construct($logged, $cookieValue) {
$this->logged = $logged;
$this->cookieValue = $cookieValue;
}
function chkCookieLogin() {
$out = new chkCookie();
if(isset($_COOKIE["logged"])) {
$out->logged = 'true';
$out->cookieValue = $_COOKIE["logged"];
return $out;
}
else {
$out->logged = 'false';
return $out;
}
}
}
$vars = chkCookieLogin();
$logged = $vars->logged;
$cookieValue = $vars->cookieValue;
echo $logged; echo $cookieValue;
Obviously it didn't work at the first attempt...and neither at the second and the third. But for the first time I feel I'm at one step to "really touch" the OOP (or this is what I think!).
My questions are:
is this attempt correctly written from the OOP point of view?
If yes, what are the problems? ('cause I guess there's more than one)
Thank you so much!
Credit to #NiettheDarkAbsol for the idea of returning an Array data-type.
Using dependency injection, you can set-up an object like this:
class Factory {
private $Data = [];
public function set($index, $data) {
$this->Data[$index] = $data;
}
public function get($index) {
return $this->Data[$index];
}
}
Then to use the DI module, you can set methods like so (using anonymous functions):
$f = new Factory();
$f->set('Cookies', $_SESSION);
$f->set('Check-Cookie', function() use ($f) {
return $f->get('Cookies')['logged'] ? [true, $f->get('Cookies')['logged']] : [false, null];
});
Using error checks, we can then call the method when and as we need it:
$cookieArr = is_callable($f->get('Check-Cookie')) ? call_user_func($f->get('Check-Cookie')) : [];
echo $cookieArr[0] ? $cookieArr[1] : 'Logged is not set';
I'd also consider adding constants to your DI class, allowing more dynamic approaches rather than doing error checks each time. IE, on set() include a constant like Factory::FUNC_ARRAY so your get() method can return the closure already executed.
You can look into using ternary operators if you're confused.
See it working over at 3v4l.org.
If it means anything, here is an OOP styled approach.

zf2 forms and object binding, without clearing non-passed values

I've read through the tutorials/reference of the Form-Component in Zend-Framework 2 and maybe I missed it somehow, so I'm asking here.
I've got an object called Node and bound it to a form. I'm using the Zend\Stdlib\Hydrator\ArraySerializable-Standard-Hydrator. So my Node-object has got the two methods of exchangeArray() and getArrayCopy() like this:
class Node
{
public function exchangeArray($data)
{
// Standard-Felder
$this->node_id = (isset($data['node_id'])) ? $data['node_id'] : null;
$this->node_name = (isset($data['node_name'])) ? $data['node_name'] : null;
$this->node_body = (isset($data['node_body'])) ? $data['node_body'] : null;
$this->node_date = (isset($data['node_date'])) ? $data['node_date'] : null;
$this->node_image = (isset($data['node_image'])) ? $data['node_image'] : null;
$this->node_public = (isset($data['node_public'])) ? $data['node_public'] : null;
$this->node_type = (isset($data['node_type'])) ? $data['node_type']:null;
$this->node_route = (isset($data['node_route'])) ? $data['node_route']:null;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
In my Controller I've got an editAction(). There I want to modify the values of this Node-object. So I am using the bind-method of my form. My form has only fields to modify the node_name and the node_body-property. After validating the form and dumping the Node-object after submission of the form the node_name and node_body-properties now contain the values from the submitted form. However all other fields are empty now, even if they contained initial values before.
class AdminController extends AbstractActionController
{
public function editAction()
{
// ... more stuff here (getting Node, etc)
// Get Form
$form = $this->_getForm(); // return a \Zend\Form instance
$form->bind($node); // This is the Node-Object; It contains values for every property
if(true === $this->request->isPost())
{
$data = $this->request->getPost();
$form->setData($data);
// Check if form is valid
if(true === $form->isValid())
{
// Dumping here....
// Here the Node-object only contains values for node_name and node_body all other properties are empty
echo'<pre>';print_r($node);echo'</pre>';exit;
}
}
// View
return array(
'form' => $form,
'node' => $node,
'nodetype' => $nodetype
);
}
}
I want to only overwrite the values which are coming from the form (node_name and node_body) not the other ones. They should remain untouched.
I think a possible solution would be to give the other properties as hidden fields into the form, however I don't wanna do this.
Is there any possibility to not overwrite values which are not present within the form?
I rechecked the code of \Zend\Form and I gotta be honest I just guessed how I can fix my issue.
The only thing I changed is the Hydrator. It seems that the Zend\Stdlib\Hydrator\ArraySerializable is not intended for my case. Since my Node-Object is an object and not an Array I checked the other available hydrators. I've found the Zend\Stdlib\Hydrator\ObjectProperty-hydrator. It works perfectly. Only fields which are available within the form are populated within the bound object. This is exactly what I need. It seems like the ArraySerializable-hydrator resets the object-properties, because it calls the exchangeArray-method of the bound object (Node). And in this method I'm setting the non-given fields to null (see code in my question). Another way would propably be to change the exchangeArray-method, so that it only sets values if they are not available yet.
So the solution in the code is simple:
$form = $this->_getForm();
$form->setHydrator(new \Zend\Stdlib\Hydrator\ObjectProperty()); // Change default hydrator
There is a bug in the class form.php, the filters are not initialized in the bindvalues method just add the line $filter->setData($this->data);
it should look like this after including the line
public function bindValues(array $values = array())
{
if (!is_object($this->object)) {
return;
}
if (!$this->hasValidated() && !empty($values)) {
$this->setData($values);
if (!$this->isValid()) {
return;
}
} elseif (!$this->isValid) {
return;
}
$filter = $this->getInputFilter();
$filter->setData($this->data); //added to fix binding empty data
switch ($this->bindAs) {
case FormInterface::VALUES_RAW:
$data = $filter->getRawValues();
break;
case FormInterface::VALUES_NORMALIZED:
default:
$data = $filter->getValues();
break;
}
$data = $this->prepareBindData($data, $this->data);
// If there is a base fieldset, only hydrate beginning from the base fieldset
if ($this->baseFieldset !== null) {
$data = $data[$this->baseFieldset->getName()];
$this->object = $this->baseFieldset->bindValues($data);
} else {
$this->object = parent::bindValues($data);
}
}
to be precious it is line no 282 in my zf2.0.6 library
this would fix your problem, this happen only for binded object situation
I ran into the same problem, but the solution of Raj is not the right way. This is not a bug as for today the code remains still similar without the 'fix' of Raj, adding the line:
$filter->setData($this->data);
The main problem here is when you bind an object to the form, the inputfilter is not stored inside the Form object. But called every time from the binded object.
public function getInputFilter()
...
$this->object->getInputFilter();
...
}
My problem was that I created every time a new InputFilter object when the function getInputFilter was called. So I corrected this to be something like below:
protected $filter;
...
public function getInputFilter {
if (!isset($this->filter)) {
$this->filter = new InputFilter();
...
}
return $this->filter;
}
I ran into the same issue today but the fix Raj suggested did not work. I am using the latest version of ZF2 (as of this writing) so I am not totally surprised that it didn't work.
Changing to another Hydrator was not possible as my properties are held in an array. Both the ObjectProperty and ClassMethods hydrators rely on your properties actually being declared (ObjectProperty uses object_get_vars and ClassMethods uses property_exists). I didn't want to create my own Hydrator (lazy!).
Instead I stuck with the ArraySerializable hydrator and altered my exchangeArray() method slightly.
Originally I had:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
$this->data = $newData;
}
This works fine most of the time, but as you can see it blows away any existing data in $this->data.
I tweaked it as follows:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
//$this->data = $newData; I changed this line...
//to...
$this->data = array_merge($this->data, $newData);
}
This preserves any existing keys in $this->data if they are missing from the new data coming in. The only downside to this approach is I can no longer use exchangeArray() to overwrite everything held in $this->data. In my project this approach is a one-off so it is not a big problem. Besides, a new replaceAllData() or overwrite() method is probably preferred in any case, if for no other reason than being obvious what it does.

Are these underscored PHP functioned ever called?

Inherited an old CakePHP site and I'm trying to figure out what some functions do. I have several functions that have the same name as another function but with an underscore first, e.g. save() and _save(). However the function _save() is never called in any context, though save() is.
I read this question and it looks like it's from an old worst-practices exercise, but that doesn't really explain why it's in my code; you still have to call function _save() as _save() right? If there's no calls to _save() is it safe to remove?
I want it gone, even the save() function wasn't supposed to be there, rewriting perfectly good framework functionality. It looks like an older version of the same function, but there's no comments and I don't know if there's some weird context in which php/Cake will fall back to the underscored function name.
Here's the code for the curious. On closer inspection it appears the underscored functions were old versions of a function left in for some reason. At least one was a "private" method being called (from a public function of the same name, minus the underscore...):
function __save() {
$user = $this->redirectWithoutPermission('product.manage','/',true);
if ($this->data) {
$this->Prod->data = $this->data;
$saved_okay = false;
if ($this->Prod->validates()) {
if ($this->Prod->save()) $saved_okay = true;
}
if ($saved_okay) {
$product_id = ($this->data['Prod']['id']) ? $this->data['Prod']['id'] : $this->Prod->getLastInsertId();
if ($this->data['Plant']['id']) {
$this->data['Prod']['id'] = $product_id;
$this->Prod->data = $this->data;
$this->Prod->save_plants();
$this->redirect('/plant/products/'.$this->data['Plant']['id']);
} else {
$this->redirect('/product/view/'.$product_id);
}
die();
} else {
die('did not save properly');
}
} else {
die('whoops');
}
}
function save() {
$user = $this->redirectWithoutPermission('product.manage','/products',true);
if ($this->data) {
$this->Prod->data = $this->data;
if ($this->Prod->validates()) {
$this->Prod->save();
$gotoURL = isset($this->data['Navigation']['goto'])?$this->data['Navigation']['goto']:'/';
$gotoURL = str_replace('%%Prod.id%%', $this->data['Prod']['id'], $gotoURL);
if (isset($this->data['Navigation']['flash'])) {
$this->Session->setFlash($this->data['Navigation']['flash']);
}
if (isset($this->params['url']['ext']) && $this->params['url']['ext']=='ajax') {
$value = array(
'success'=>true
,'redirect'=>$gotoURL
);
print $this->Json->encode($value);
} else {
$this->redirect($gotoURL);
}
} else {
$value = array(
'success'=>false
,'message'=>"You have invalid fields."
,'reason'=>'invalid_fields'
,'fields'=>array(
'Prod'=>$this->Prod->invalidFields()
)
);
print $this->Json->encode($value);
}
} else {
$this->redirect('/products');
}
die();
}
I had hoped to learn whether or not some convention applied to this situation, but from testing I've found the functions are not called which is really the answer to the question I asked.

An example of an MVC controller

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);
}
?>

Why is this Zend Framework _redirect() call failing?

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

Categories