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);
}
?>
Related
Imagine I have a URL that loads a controller if the name matches (Ignoring any security issues with this at the moment hehe)
public function Load( $controller, $action = "Index" )
{
require_once( "Controllers/" . $controller . "Controller.php" );
$controllerName = $controller . "Controller";
$loadedController = new $controllerName();
$actionName = "ActionResult_" . $action;
$loadedController->$actionName();
}
Now imagine I want a log in form to send its $_POST details as parameters of the receiving controller launched above:
<?php
class ExcelUploadController extends Controller
{
public function ActionResult_Login( $username = NULL, $password = NULL )
{
// The parameters need to be mapped to the $_POST parameters names probably from the Load method somewhere and pumped in to the $loadedController->$actionName();
$post_username = $username;
$post_password = $password;
$this->ReturnView( "ExcelUpload/Index" );
}
}
?>
But also so that it does not matter what order the parameters are declared, it matches the parameter in the function based on the $_POST key.
How might I go about doing this, any ideas?
So to clarify if this doesn't make sense.. the method might look something like this:
public function Load( $controller, $action = "Index" )
{
require_once( "Controllers/" . $controller . "Controller.php" );
$controllerName = $controller . "Controller";
$loadedController = new $controllerName();
$actionName = "ActionResult_" . $action;
$checkIfPostData = $_POST;
if( isset( $checkIfPostData ) )
{
// Do some funky wang to map the following $loadedController->$actionName();
// with the username and password or any other $_POST keys so that in the calling method, I can grab hold of the $_POST values
}
$loadedController->$actionName();
}
What your are looking for is call_user_func_array()
EDIT, to reply to comment :
You have two options: rewrite all your function so that they accept only one array() as argument and you parse that array for values. A bit fastidious but it can be useful in some cases. Or you can request for the required argument of a function:
// This will create an object that is the definition of your object
$f = new ReflectionMethod($instance_of_object, $method_name);
$args = array();
// Loop trough params
foreach ($f->getParameters() as $param) {
// Check if parameters is sent through POST and if it is optional or not
if (!isset($_POST[$param->name]) && !$param->isOptional()) {
throw new Exception("You did not provide a value for all parameters");
}
if (isset($_POST[$param->name])) {
$args[] = $_POST[$param->name];
}
if ($param->name == 'args') {
$args[] = $_POST;
}
}
$result = call_user_func_array(array($instance_of_object, $method_name), $args);
That way your array will be properly constructed.
You can also add some specific treatment whether a parameter is optional or not (I guess you can understand how to do it from the code I gave you ;)
Since the data is being sent through POST you don't need to pass any parameters to your method:
class ExcelUploadController extends Controller {
private $userName;
private $login;
public function ActionResult_Login() {
$this->userName = $_POST['username'];
$this->login = $_POST['login'];
}
}
Don't forget to sanitize and validate the user input!
Following this tutorial http://johnsquibb.com/tutorials/mvc-framework-in-1-hour-part-one Im trying to write my first MVC Blog.
I understood how it works, router.php Calls the suitable page controller. This controller calls the model, Then calls the View page with the returned value.
My question is, What if I want to add to the same news.php page a header\footer. Normally I would write "include("header.php"). But now when using MVC, how could I implement that?
These are my files:
router.php:
<?php
/**
* This controller routes all incoming requests to the appropriate controller
*/
//Automatically includes files containing classes that are called
//fetch the passed request
$pageURL = $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
$path_parts = pathinfo($pageURL);
$page_name = $path_parts['filename'];
$parsed = explode('?' , $page_name);
//the page is the first element
$page = array_shift($parsed);
// If there is any variables, GET them.
if(!empty($parsed))
{
$parsed = explode('&' , $parsed[0]);
$getVars = array();
foreach ($parsed as $argument)
{
//explode GET vars along '=' symbol to separate variable, values
list($variable , $value) = explode('=' , $argument);
$getVars[$variable] = urldecode($value);
}
}
//compute the path to the suitable file
$target ='controllers/' . $page . '_controller.php';
//get target controller
if (file_exists($target))
{
include_once($target);
//modify page to fit naming convention
$class = ucfirst($page) . '_Controller';
//instantiate the appropriate class
if (class_exists($class))
{
$controller = new $class;
}
else
{
//did we name our class correctly?
die('class does not exist!');
}
}
else
{
//can't find the file in 'controllers'!
die('page does not exist!');
}
//once we have the controller instantiated, execute the default function
//pass any GET varaibles to the main method
$controller->main($getVars);
// AutoLoad
function __autoload($className)
{
// Parse out filename where class should be located
// This supports names like 'Example_Model' as well as 'Example_Two_Model'
list($suffix, $filename) = preg_split('/_/', strrev($className), 2);
$filename = strrev($filename);
$suffix = strrev($suffix);
//select the folder where class should be located based on suffix
switch (strtolower($suffix))
{
case 'model':
$folder = '/models/';
$filename = ($className);
break;
case 'library':
$folder = '/libraries/';
break;
case 'driver':
$folder = '/libraries/drivers/';
break;
}
//compose file name
$file = SERVER_ROOT . $folder . strtolower($filename) . '.php';
//fetch file
if (file_exists($file))
{
//get file
include_once($file);
}
else
{
//file does not exist!
die("File '$filename' containing class '$className' not found in
'$folder'.");
}
}
?>
post_controller.php
<?php
/**
* This file handles the retrieval and serving of posts posts
*/
class Posts_Controller
{
/**
* This template variable will hold the 'view' portion of our MVC for this
* controller
*/
public $template = 'posts';
/**
* This is the default function that will be called by router.php
*
* #param array $getVars the GET variables posted to index.php
*/
public function main(array $getVars)
{
//$b_controller =new Bottom_Bar_Controller;
//$b_controller->main($getVars);
$postsModel = new Posts_Model;
//get an post
$post = $postsModel->get_post($getVars['id']);
//create a new view and pass it our template
$header = new View_Model('header_template');
$bottom_bar = new View_Model('bottom_bar');
$view = new View_Model($this->template);
//assign post data to view
$view->assign('header', $header->render(FALSE));
$view->assign('bottom', $bottom_bar->render(FALSE));
$view->assign('title' , $post['title']);
$view->assign('content' , $post['content']);
$view->assign('date' , $post['date']);
$view->assign('by' , $post['added_by']);
$view->render();
}
}
posts_model.php
<?php
/**
* The Posts Model does the back-end heavy lifting for the Posts Controller
*/
class Posts_Model
{
/**
* Holds instance of database connection
*/
private $db;
public function __construct()
{
$this->db = new MysqlImproved_Driver;
}
/**
* Fetches article based on supplied name
*
* #param string $author
*
* #return array $article
*/
public function get_post($id)
{
//connect to database
$this->db->connect();
//sanitize data
$author = $this->db->escape($id);
//prepare query
$this->db->prepare
(
"
SELECT * FROM `posts`
WHERE
`id` = '$id'
LIMIT 1
;
"
);
//execute query
$this->db->query();
$article = $this->db->fetch('array');
return $article;
}
}
?>
view_model.php
<?php
/**
* Handles the view functionality of our MVC framework
*/
class View_Model
{
/**
* Holds variables assigned to template
*/
private $data = array();
/**
* Holds render status of view.
*/
private $render = FALSE;
/**
* Accept a template to load
*/
public function __construct($template)
{
//compose file name
$file = SERVER_ROOT . '/views/' . strtolower($template) . '.php';
if (file_exists($file))
{
/**
* trigger render to include file when this model is destroyed
* if we render it now, we wouldn't be able to assign variables
* to the view!
*/
$this->render = $file;
}
}
/**
* Receives assignments from controller and stores in local data array
*
* #param $variable
* #param $value
*/
public function assign($variable , $value)
{
$this->data[$variable] = $value;
}
/**
* Render the output directly to the page, or optionally, return the
* generated output to caller.
*
* #param $direct_output Set to any non-TRUE value to have the
* output returned rather than displayed directly.
*/
public function render($direct_output = TRUE)
{
// Turn output buffering on, capturing all output
if ($direct_output !== TRUE)
{
ob_start();
}
// Parse data variables into local variables
$data = $this->data;
// Get template
include($this->render);
// Get the contents of the buffer and return it
if ($direct_output !== TRUE)
{
return ob_get_clean();
}
}
public function __destruct()
{
}
}
posts.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="../style/style.css" rel="stylesheet" type="text/css" media="screen" />
<title> Posts (View)</title>
</head>
<body>
<div id="main">
<div class="container">
<?=$data['header'];?>
<div id="content">
<div class="content-background">
<h2> <?=$data['title'];?></h2>
<h4> <?=$data['date'];?> </h4>
<p><?=$data['content'];?></p>
</div>
</div>
</div>
</div>
</body>
</html>
Part of the problem is, that your tutorial has a pretty primitive interpretation of MVC-inspired design pattern (you actually cannot implement classical MVC in PHP, but there are patterns, that are based on it).
View is not just a template. Views are supposed to be class instances, which contain all presentation logic and deal with multiple templates. What you actually have there is a layout template which contains posts template.
// class \Application\View\Posts
public function render()
{
$layout = new Template( $this->defaultTemplateDirectory . 'layout.html');
$content = new Template( $this->defaultTemplateDirectory . 'posts.html' );
$layout->assign( 'content' , $content->render() );
return $layout->render();
}
Also, one of the things, that a view instance should do, is requesting information from the model layer.
Few materials that you might find useful:
Model-View-Confusion part 1: Why the model is accessed by the view in MVC
Simple PHP Template Engine
How should a model be structured in MVC?
And if you want to expand you knowledge in OOP, this post contains a list of recommended lectures and books.
There is no good solution in the MVC structure for that. You can find a solution in every framework. In CakePHP for example you can use an AppController, a controller which is always called for every requests. Sort of base controller.
But no, not very nice to do.
Most simple one is to ask the data directly from the view. That sounds weird but it is accepted in the MVC structure. So in your view you call $News->getLatestItems(10); and work with them.
I don't like it but it works.
If you have lots of widgets and blocks MVC alone might just not be the right structure. Then you could take a look at things like: http://techportal.inviqa.com/2010/02/22/scaling-web-applications-with-hmvc/ which is a derivate of MVC.
Another solution which you see more and more: Request it via AJAX calls. So just load them after the page has loaded. That way you also solve the issue since one MVC request then becomes multiple MVC requests.
I am using Kohana 3.2 and I am having problems calling the ouput of a controller in another controller.
What I want...
In some pages I have got a menu, and in others I don't. I want to use make use of the flexability of the HMVC request system. In the controller of a page I want to call another controller which is responsible for the creation of the menu.
What I have a the moment:
file menu.php:
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Menu extends Controller
{
private $_model = null;
public function __construct(Request $request, Response $response)
{
parent::__construct($request, $response);
$this->_model = Model::factory('menu');
}
public function action_getMenu()
{
$content = array();
$content['menuItems'] = $this->_model->getMenuItems();
// Render and output.
$this->request->response = View::factory('blocks/menu', $content);
//echo '<pre>'; print_r($this->request->response->render()); echo '</pre>'; die();
}
}
somepage.php
public function action_index()
{
$this->template->title = 'someTitle';;
$contentData['pageTitle'] = 'someTitle';
$contentData['contentData'] = 'someData';
#include the menu
$menuBlock = Request::factory('menu/getMenu')->execute();
$menuData = array('menu' => $menuBlock);
$this->template->menu = View::factory('pages/menu')->set('menu',$menuData);
$this->template->content = View::factory('pages/somePage', $contentData);
$view = $this->response->body($this->template);
$this->response->body($view);
}
If I uncomment the following line in menu.php, I see the menu rendered:
//echo '<pre>'; print_r($this->request->response->render()); echo '</pre>'; die();
So I guess that part is alright. The problem is in the following line in somepage.php:
$menuBlock = Request::factory('menu/getMenu')->execute();
This gives me back a response object. Whatever I do, I do not get the output in $this->template->menu.
$this->template->menu = View::factory('pages/menu')->set('menu',$menuData);
What must I do to have $this->template->menu contain the view, so I can use it correctly?
I hope this all makes sense. This is the way I would like to do it, but maybe I am completely on the wrong track.
I would do it this way:
class Controller_Menu extends Controller
{
public function action_build()
{
// Load the menu view.
$view = View::factory('navigation/menu');
// Return view as response-
$this->response->body($view->render());
}
}
In your controller get the menu as follows:
// Make request and get response body.
$menu = Request::factory('menu/build')->execute()->body();
// e.g. assign menu to template sidebar.
$this->template->sidebar = Request:.factory('menu/build')->execute()->body();
I would not use the __construct method in your controllers. Use before() instead, this is sufficient for most of the problems (for example auth):
public function before()
{
// Call aprent before, must be done here.
parent::before();
// e.g. heck whether user is logged in.
if ( !Auth::instance()->logged_in() )
{
//Redirect if not logged in or something like this.
}
}
I found the answer to my problem in less than an hour after asking.
I just forgot to put it here.
In somePage.php change :
$menuBlock = Request::factory('menu/getMenu')->execute();
$menuData = array('menu' => $menuBlock);
$this->template->menu = View::factory('pages/menu')->set('menu',$menuData);
To:
$this->template->menu = Request::factory('menu/getMenuBlock')->execute()->body();
And in menu.php change:
$this->request->response = View::factory('blocks/menu', $content);
To:
$request = View::factory('blocks/menu', $content);
$this->response->body($request);
I hope this will help someone else.
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"/>';
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.