I have some questions concerning the Zend Framework. I am attempting to route all static pages through the default controller using the now default displayAction() method. The intention is to have the displayAction() process the request by looking at the page param, determine if the script page exists, if it does render the view otherwise throw a 404 page not found error. Additionally, a test is done to see if a method with the same name as the param exists, if so, call on that action.
Listed here is the routing configuration from the application.ini
resources.router.routes.static-pages.route = /:page
resources.router.routes.static-pages.defaults.module = default
resources.router.routes.static-pages.defaults.controller = index
resources.router.routes.static-pages.defaults.action = display
Here is the controller actions:
public function someAction() {
// do something
}
public function displayAction() {
// extract page param, e.g. 'some'
$page = $this->getRequest()->getParam('page');
// create zend styled action method, e.g. 'someAction'
$page_action = $page.'Action';
// if a method within this controller exists, call on it
if (method_exists($this, $page_action)) {
$this->$page_action();
}
// if nothing was passed in page param, set to 'home'
if (empty($page)) {
$page = 'home';
}
// if script exists, render, otherwise, throw exception.
if (file_exists($this->view->getScriptPath(null)."/".$this->getRequest()->getControllerName()."/$page.".$this->viewSuffix)) {
$this->render($page);
} else {
throw new Zend_Controller_Action_Exception('Page not found', 404);
}
}
Now, here are my questions: Is there a better way of doing this? I'm relatively new to this framework, so are there best practices which apply? Is there a better way of calling on an action from within a controller? I have done A LOT of looking around through the documentation, however, quite a bit of it seems to contradict itself.
Update 1:
After having a think and a read, I've managed to simplify the solution and include a few things which were mentioned. NOTE: I use PagesController as my default static-content controller.
Listed here is the routing configuration from the application.ini. For calls to the home page i.e. "/", I pass "home" as the action param, for all other requests, the user defined / url link param is sent in action.
resources.router.routes.home.route = "/"
resources.router.routes.home.defaults.module = "default"
resources.router.routes.home.defaults.controller = "pages"
resources.router.routes.home.defaults.action = "home"
resources.router.routes.pages.route = "/:action"
resources.router.routes.pages.defaults.module = "default"
resources.router.routes.pages.defaults.controller = "pages"
Here is the controller actions. If user define parameter exists as an action, it will be called, else it falls to the php magic function __call.
public function someAction()
{
// Do something
}
public function __call($method, $args)
{
// extract action param, e.g. "home"
$page = $title = $this->getRequest()->getParam('action');
// test if script exists
if (file_exists($this->view->getScriptPath(null) . "/"
. $this->getRequest()->getControllerName() . "/$page . " . $this->viewSuffix))
{
// pass title to layout
$this->view->assign(compact('title'));
// render script
$this->render($page);
} else {
throw new Zend_Controller_Action_Exception('Page not found', 404);
}
}
It works. So, here are my questions: Would you consider standardising on using this method to manage static content? If not, why not? How would you improve it? Also, considering this is a GET request, would it be a wise move to use Zend_Filter_input to cleanse input or is that just overkill?
Your approach seems reasonable to me. However, perhaps you should take advantage of the __call method instead, which would allow you to more easily route your actions...
Setup your route like this:
resources.router.routes.static-pages.route = /:action
resources.router.routes.static-pages.defaults.module = default
resources.router.routes.static-pages.defaults.controller = index
And your controller like so:
public function someAction() {
//going to URL /some will now go into this method
}
public function __call($name, $args) {
//all URLs which don't have a matching action method will go to this one
}
I think your on the right track however here are some other ideas.
Break up your routing per sections in your INI:
ie a blog router, a static page router a forum router etc.. (I think you are already doing this)
Use the various router classes to handle routing per section rather than sending it to a controller.
Static:
http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.static
All:
http://framework.zend.com/manual/en/zend.controller.router.html
Some links that may help:
codeutopia.net/blog/2007/11/16/routing-and-complex-urls-in-zend-framework/
www.vayanis.com/2009/03/20/intro-to-zend-framework-routing/
Related
I'm looking to send the user to another page via a controller method. The other page expects POST data.
Normally, the page is accessed with a postLink(). Is there a way to use this in the controller, perhaps with redirect()?
A little bit old but still no answer accepted so...
The answer is no, and yes.
No, there is no direct method since you cannot pass POSTed data using redirect() function.
You could use requestAction(), since you can pass data as posted (see requestAction() here for version cakePHP>=2.0).
In that case you pass an url and then an array having the key data with the posted data, something like
$this->requestAction($url, array('data' =>$this->data));or if you prefer$this->requestAction($url, array('data' =>$this->request->data));
The problem with requestAction() is that the result is environmentally as if you were generating the page of the requested action in the current controller, not in the target, resulting in not very satisfactory effects (at least not usually for me with components behaving not very nicely), so still, no.
...but Yes, you can do something very similar using the Session component.
This is how I usually do it. The flow would be something like this:
View A=>through postLink() to Action in A controller=>=>A controller request->data to Session variable=>=>action in B controller through redirect()=>=>set B controller request->data from Session variable=>=>process data in B controller action=> View B
So, in your A controller, let's say in the sentToNewPage() action you would have something like
//Action in A controller
public function sentToNewPage()
{
$this->Session->write('previousPageInfo', $this->request->data);
$url = array('plugin' => 'your_plugin', 'controller'=>'B',
'action'=>'processFromPreviousPage');
$this->redirect($url);
}
and in B controller:
//Action in B controller
public function beforeFilter()
{//not completelly necessary but handy. You can recover directly the data
//from session in the action
if($this->Session->check('previousPageInfo'))
{$this->data = $this->Session->read('previousPageInfo')};
parent::beforeFilter();
}
public function processFromPreviousPage()
{
//do what ever you want to do. Data will be in $this->data or
// if you like it better in $this->request->data
$this->processUserData($this->request->data);
//...
}
Best solution would be use javascript to redirect.
But if you want more cake I give you some tools
CakeAPI: requestAction - it allow to execute controller method of desire with parameters, if you pass 'return', it will return full view output for that action.
//very useful in views
$result = $this->requestAction('Controller/method',
array('return','user_id'=>$userId)
);
parameter will be accessible in controller via request param
$this->request->params['user_id']
Long and short is that it's not easy to emulate an HTML form with POST data and a redirect, you kind of need to set a bunch of hidden variables containing the data and automatically post the form to your destination via Javascript.
What I would do is take the processing functionality out of the function that requires POST variables, and make it generic so that you can call it from both of your functions.
Consider this rough example:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// do stuff
echo $name . ', ' . $age;
}
Let's say that is the action you are trying to post data to in this scenario, but you can't because you can't emulate those $_POST variables over a redirect without the scenario mentioned at the top here. You can do this:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// call common function
echo $this->getMyResults($name, $age);
}
// accessible from inside the controller only
private function getMyResults($name, $age) {
return $name . ', ' . $age;
}
Now you can also use that getMyResults() functionality by passing regular old variables into it:
public function myProblemFunction() {
$name = 'John';
$age = 15;
echo $this->getMyResults($name, $age);
}
Now, obviously you won't be outputting anything like that straight from the controller action, you'll be setting it to your views etc, but that's an example of how you can centralize functionality to be used in multiple locations.
For the disclaimer, this kind of thing is exactly what models are for, and you should definitely consider putting this kind of function into a model instead of a controller - it depends on your specific application.
With cakephp 2.x Use this:
$this->autoRender = false;
$request = new CakeRequest(Router::url(array('controller' => 'mycontroller','action' => 'my_action')));
$request->data('dataIndex','value');
$response = new CakeResponse();
$d = new Dispatcher();
$d->dispatch(
$request,
$response
);
but it will not redirect but dispatch to a different controller/action so if you went on /controller/oldaction it will stays the current url (no HTTP redirection is done).
You could still change the url with javascript
see: Change the URL in the browser without loading the new page using JavaScript
We are creating an application where we are able to create pages inside our DB. All of our domains are going to point to this application, so we are storing our domains also in the DB. Also based on that domain we are creating new layouts and determining what pages belong to that domain. With that being said here is our issue:
We put a rule inside the urlMangaer:
'http://dev.<domain:\w+>.com'=>'site/view'
which outputs: http://dev.example.com/example/index which represents: http://dev.$domain.com/$domain/$page
Our goal is for the url to read http://dev.$domain.com/$page and still have the functionality we need.
Here is our action call: public function actionView($domain = null,$page = null, $parm = null){}
A possible solution we thought of is extending the urlManager but we don't know where to begin to do so.
what about defining new action in your siteController, or find a way using actionIndex, to get both domain and page as input and render the corresponding domain, page from DB.
I figured out the solution, it was in my controller all along.
I created a new function called getDomain:
public function getDomain(){
$domain = Domain::model()->find('domain_name=:domain_name', array(':domain_name'=>Yii::app()->request->baseUrl));
return $domain->domain_name;
}
then inside my actionView function
public function actionView($domain = null,$page = null, $parm = null){
if ($domain === null){
$domain = $this->getDomain();
} ...
Now it works with out the $domain having to be inside the URL
I'm trying to create my own MVC framework in PHP to learn about them but I'm currently having trouble implementing the action of the controller.
The problem is that some controllers have one or multiple arguments such as actionView($id), while others have no arguments like actionCreate(). How do I handle these differently?
For example:
$action; //contains my action
$params; //array containing my arguments
In my controller I would call...
$this->$action(); //for no parameters
$this->$action($params[0]); //for one parameter
$this->$action($params[0], $params[1]); //for two parameters
... and so on
It wouldn't be feasible to handle every possible situation like this. Perhaps I am implementing the actions wrong. Can someone guide me in the right direction?
EDIT: How do the MVC frameworks out there make handling multiple arguments possible?
I would simply pass the array of arguments as the only argument to every action. Let it be known that your framework's convention includes passing arguments to actions in this way. This allows the implementing code to choose how they want to deal with it; use func_get_args() maybe or provide a paramater in the method signature:
public function action(array $args = array()) { ...
Something to be wary of though is that although your action may require a parameter the user may not actually provide one. If your method signature is setup to require a value be passed and you don't have enough values to plug in then you may get all kinds of errors. This is part of the reason I would choose to simply pass in the array of parameters and always pass an array, even if it is empty.
Alternatively, partly influence off of BrNathanH answer, you could somehow tie an object to a given action and then inject that array into the object's constructor. That object can be responsible for providing the data needed for the action and giving suitable defaults/checking for the values to exist. You could then just add an object signature to your actions, if you wanted to be doubly sure that they are getting the appropriate data. I'm not sure exactly how you would implement this in your own code, the primary problem being the mapping of controller::action to the appropriate "ParamObject". But wrapping the array into objects might be a good idea and would solve the problem of not knowing how many parameters to pass. It's always one, the "ParamObject" associated with that action.
The best way I know how to explain this is with examples, so here is an example of my own implementation for a PHP-MVC application. Make sure to pay close attention to the Hook::run function, which handles the arguments for loading the control.
index.php:
<?php
class Hook {
const default_controller = 'home';
const default_method = 'main';
const system_dir = 'system/';
static function control ($name, $method, $parameter) {
self::req(Hook::system_dir.$name.'.php'); // Include the control file
if (method_exists('Control', $method)) { // For my implementation, I can all control classes "Control" since we should only have one at a time
if (method_exists('Control', 'autoload')) call_user_func(array('Control', 'autoload')); // This is extremely useful for having a function to execute everytime a particular control is loaded
return call_user_func(array('Control', $method), $parameter); // Our page is actually a method
}
throw new NotFound($_GET['arg']); // Appear page.com/control/method does not exist, so give a 404
}
static function param ($str, $limit = NULL) { // Just a wrapper for a common explode function
return (
$limit
? explode('/', rtrim($str, '/'), $limit)
: explode('/', rtrim($str, '/'))
);
}
static function req ($path) { // Helper class to require/include a file
if (is_readable($path)) return require_once($path); // Make sure it exists
throw new NotFound($path); // Throw our 404 exeception if it doesn't
}
static function run() {
list($name, $method, $parameter) = ( // This implementaion expects up to three arguements
isset($_GET['arg'])
? self::param($_GET['arg'], 3) + array(self::default_controller, self::default_method, NULL) // + array allows to set for default params
: array(self::default_controller, self::default_method, NULL) // simply use default params
);
return self::control($name, $method, $parameter); // Out control loader
}
}
class AuthFail extends Exception {}
class UnexpectedError extends Exception {}
class NotFound extends Exception {}
try {
Hook::run();
}
catch (AuthFail $exception) { // Makes it posssible to throw an exception when the user needs to login
// Put login page here
die('Auth failed');
}
catch (UnexpectedError $exception) { // Easy way out for error handling
// Put error page here
die('Error page');
}
catch (NotFound $exception) { // Throw when you can't load a control or give an appearance of 404
die('404 not found');
}
system/home.php:
<?php
class Control {
private function __construct () {}
static function autoload () { // Executed every time home is loaded
echo "<pre>Home autoload\n";
}
static function main ($param='') { // This is our page
// Extra parameters may be delimited with slashes
echo "Home main, params: ".$param;
}
static function other ($param='') { // Another page
echo "Home other, params:\n";
$param = Hook::param($param);
print_r($param);
}
}
.htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?arg=$1 [QSA,L]
With this htaccess file, we are able to load the home.php control using localhost/home/main. Without it, our urls would look like localhost/index?args=home/main.
Demonstration screenshot 1 (localhost/simple_mvc/home/main/args1/args2/args3):
You don't always know how many arguments an particular control method is going to expect, which is why I believe it is best to pass a single argument delimited by slashes. If the control method expects more than one argument, you can then use the provided Hook::param function to further evaluate.
Demonstration screenshot 2 (localhost/simple_mvc/home/other/args1/args2/args3):
To answer your question:
The key file here that really helps to answer your question is the .htaccess file, which transparently turns localhost/these/types/of/urls into something like localhost/index.php?args=these/types/of/urls. That then allows you to split the arguments using explode($_GET['args'], '/');.
A GREAT video tutorial on this subject is here. It really helps to explain a lot.
I've made some changes to home.php and index.php with the latest edit
You could just pass the whole array. The problem is that each controller has to do error checking to see if the correct values are there. Another solution would be to create a data object that holds the parameters. You have the controller access that (which does all the error checking for you).
For arguments, you can always set some arguments to be optional. Like this:
function($argument='', $argument2='')
{
// ...
}
This means that $argument and $argument2 are optional. If those arguments are set when the function is called... those arguments will have values.
Although, passing an array() (and then using isset() to check for set array keys or items,) would be easier for some functions.
I'm new to MVC in PHP and I was just wondering what the best way to do this is?
At the moment I've got a simple setup like this:
Model
User.php
Controller
controller.php
View
login.php
register.php
my_account.php
The model has all the database functionality for logging in and registering, and the view files have the relevant working forms.
My main question is, what is the best way to have the controller call pages? Currently, it looks something like:
public function show_page()
{
if ($_GET['p'] == "login")
{
include('View/login.php');
if (isset($_POST['username']))
{
$this->user->login($_POST['username'], $_['pass']
}
}
if ($_GET['p'] == "register") { include('View/register.php'); }
if ($_GET['p'] == "my_account") { include('View/my_account.php'); }
}
This doesn't seem logical, am I doing it wrong?
I think that the best way is to use some kind of routing system so you have a map somewhere with the possible url pattern / page to show combinations and after deciding which controller to call you can load the appropriate view in your controller.
What you presented here seems somewhat blurred to me. I think that you should check out implementations out there like Pure mvc or symfony so you can get a grip on the concept quickly. I believe that you (or anyone else for that matter) shouldn't reinvent the wheel but study, understand and improve what you can get.
If you are going to create your own MVC framework then you should check out the basic MVC concepts and plan your software before trying to write it.
Most PHP based MVC frameworks that I've used utilize a front controller. The .htaccess file directs all traffic to a single (usually index.php) file (usually) in the root of the project.
This file is responsible for determining which application controller to load. That controller is then responsible for any and all application level logic.
in a framework I wrote, in my front controller I do this
$page = tgsf_parse_url();
require resolve_controller( $page );
The tgsf_parse_url function in the above code parses $_SERVER['REDIRECT_URL'] to determine what variables are being passed.
the resolve_controller in the above code handles plugin hooks and 404 conditions, but the bottom line is that it always returns a path to send to include/require so that variable scoping doesn't become an issue (including in a function limits variable scope)
Any variables that are set in the controller are automatically available in a view when you include a view like this:
// this is an application level controller file
$name = 'Mr. Example';
include view( 'example' );
Then in the view file:
<h2><? echo $name; ?></h2>
What you have is not dynamic at all.
This is how I do it in my MVC:
private function loadView(){
//Bring the variables to the global scope
extract($this->getAll()); //load all variables into local scope
if(Config::get('view')){
$template_file = 'view/' . Config::get('view') . '/' . Config::get('method') . '.stp';
if(is_file($template_file)){
include $template_file;
}
else {
include 'view/missingview.stp'; //no such view error
}
}
else {
Config::set('template', 'blank');
include 'view/missingfunction.stp'; //no such function error
}
}
Have something set the view somewhere in the code.
This is how I set the view in the controller:
public function __construct(SessionManager $SESSION) {
$this->pageOn = Config::get('page');
$this->session = $SESSION;
$model_name = $this->name;
if(class_exists($model_name) && is_subclass_of($model_name, 'AppModel')){
/**
* #var AppModel $model_name
*/
$this->$model_name = new $model_name();
}
else {
//default model (no database table chosen)
$this->$model_name = new AppModel();
}
/* Get all posts */
$this->posts = $this->$model_name->getPosts();
Config::set('view', strtolower($model_name)); //<<RIGHT HERE!
if(!$this->session->get(strtolower($model_name))){
$this->session->set(strtolower($model_name), array());
}
}
I am currently working on CMS for a client, and I am going to be using Codeigniter to build on top of, it is only a quick project so I am not looking for a robust solution.
To create pages, I am getting to save the page details and the pull the correct page, based on the slug matching the slug in the mysql table.
My question is however, for this to work, I have to pass this slug from the URL the controller then to the model, this means that I also have too have the controller in the URL which I do not want is it possible to remove the controller from the URL with routes?
so
/page/our-story
becomes
/our-story
Is this possible
I would recommend to do it this way.
Let's say that you have : controller "page" / Method "show"
$route['page/show/:any'] = "$1";
or method is index which I don't recommend, and if you have something like news, add the following.
$route['news/show/:any'] = "news/$1";
That's it.
Yes, certainly. I just recently built a Codeigniter driven CMS myself. The whole purpose of routes is to change how your urls look and function. It helps you break away from the controller/function/argument/argument paradigm and lets you choose how you want your url's to look like.
Create a pages controller in your controllers directory
Place a _remap function inside of it to catch all requests to the controller
If you are using the latest version of CI 2.0 from Bitbucket, then in your routes.php file you can put this at the bottom of the file: $routes['404_override'] = "pages"; and then all calls to controllers that don't exist will be sent to your controller and you then can check for the presence of URL chunks. You should also make pages your default controller value as well.
See my answer for a similar question here from a few months back for example code and working code that I use in my Codeigniter CMS.
Here's the code I used in a recent project to achieve this. I borrowed it from somewhere; can't remember where.
function _remap($method)
{
$param_offset = 2;
// Default to index
if ( ! method_exists($this, $method))
{
// We need one more param
$param_offset = 1;
$method = 'index';
}
// Since all we get is $method, load up everything else in the URI
$params = array_slice($this->uri->rsegment_array(), $param_offset);
// Call the determined method with all params
call_user_func_array(array($this, $method), $params);
}
Then, my index function is where you would put your page function.