In the documentation of FuelPHP, it has the following sample:
// or fetch the output of a module
$widget = Request::forge('mymodule/mycontroller/mymethod/parms', false)->execute();
echo $widget;
This works when the function I am calling has the action_ prefix, but when I remove the prefix (since I don't want it to be called by the browser) it doesn't work anymore even if I set the 2nd parameter to false.
Here is an example:
WORKS
In one controller I call:
$widget = Request::forge('mymodule/mycontroller/mymethod')->execute();
echo $widget;
In mycontroller:
public function action_mymethod()
{
echo 'works';
}
FAILS with 404
In one controller I call:
$widget = Request::forge('mymodule/mycontroller/mymethod', false)->execute();
echo $widget;
In mycontroller:
public function mymethod()
{
echo 'works';
}
You can not remove the action prefix.
You don't understand how HMVC in FuelPHP works. From the controllers point of view, a request is a request, no matter where it comes from. Any action can be called either from the URL (the main request) or through secondary requests (HMVC).
The second parameter of the Request::forge() method just controls the routing. If true (the default), the request is send through the routing engine, so routes will apply when mapping the request URI to a controller/method. If false, the routing engine is bypassed, and a direct mapping is made to the controller/method.
You will need this if your routing table contains a catch-all at the end to avoid routing to internal controllers. This is the preferred way of shielding controllers from being called through a main request.
If you have controllers with both public and internal methods, using the route option can become complex as you need to exclude some URI's from the catch_all.
In that case you can check the request type in the controller action using:
\Request::is_hmvc()
This will return false if your action is called by the main request (i.e. via the browser URL) or true if it was an HMVC call. You can redirect elsewhere, or throw a HttpNotFoundException if you want your 404 to be shown.
Related
I am trying to learn a PHP framework. But I'm having some difficulties understanding some of the concepts of routing.
I've chosen to use Flight. Their homepage shows:
require 'flight/Flight.php';
Flight::route('/', function(){
echo 'hello world!';
});
Flight::start();
And I don't understand what they're using Flight::route... for. What am I missing? This question isn't even related to Flight. It's related to just routing in general.
Routing basically maps HTTP requests to your methods/functions.
To put it simply, say you have the route:
Flight::route('/page1', function() {
echo 'page1!';
});
This is was basically happens:
Client requests example.com/page1
Server sends query to PHP
Your PHP framework parses the request URL
Picks the correct route, in our case, page1/
And finally calls the function you passed in, so basically echo 'page1';
What seems to be happening in your file (I'm not familiar with Flight)
the require 'flight/Flight.php'; is more than likely defining a class for all the routing.
Then Flight::route(); Is simply using the route() method from the class Flight without an instance of the class.
Flight::route('/', function(){
echo 'hello world!';
});
What happens here is when a route is matched (by matched means the URI of the user matches the URI on your route, in this case www.yourdomain.com/ will match the '/' route) And then the code inside the function() callback gets executed.
If you add another route
Flight::route('/about', function(){
echo 'About Us';
});
When the user visits www.yourdomain.com/about He will get whats inside that route.
Flightphp has a rather comprehensive explanation on how to set up routes here.
You should see routes as definitions of how to handle different request patterns.
The example on Flight's home page says that if you hit your site root (i.e. /) it will simply return "hello world!" as a response.
If you read further on the Flightphp install page you will notice that all requests are handled by the index.php page. And thus depending on the routes you define it replies with the relevant response defined for that url request pattern.
Flight::route('/', function(){
echo 'hello world!';
});
This snippet is heart of your project.
This will accept two parameters.
Route
Method to call on this route call
Consider below code snippet, If you are having your project directory http://localhost/flight_project/, when anyone requests this directory, function defined as 'function_here' will be called.
Flight::route('/', 'function_here');
If you have defined route like below,
Flight::route('/user/', function(){
// do something here
});
when someone access http://localhost/flight_project/user/, the above in-line function gets called.
More info HERE
route() is a static function it seems, that means it's not specific to the object, i.e. you can't create an object such as
$flight = new Flight();
and then call
$flight->route(...)
but rather you call it via the class (not an object, which is a specific implementation of the class). You call static functions of a class by using ::, in this case
Flight::route(...)
The content of the route just says, when you encounter '/', do 'X'... and in your case 'X' is
function(){
echo 'hello world!';
}
in later stages you get to match stuff like
'/' (homepage, i.e. "mywebsite.com/")
'/about-us' (About Us page, i.e. "mywebsite.com/about-us")
'/user/{id}' (User page, i.e. you can pass a parameter such as "mywebsite.com/user/taylor" and then get the user data)
or whatever you want. And instead of just writing the function into the routing file, you can tell the router to go to a specific function (usually a Controller function) and you can do more stuff there.
I hope this helps!
Im creating a MySQL database driven PHP (W)CMS application which follows the MVC pattern. First take a look at the framework:
The MVC framework handles the request and decides what to load/call based on the URL, like: http://domain.com/user/details/121 will load and instantiate a User controller object, and calls its details(121) method with the userid passed as a parameter, and then instantiate a User_Model and "ask" it for the detailed data of the user with the 121 userid, and at last display the result with a View. This is the basic concept of an MVC architecture. Nothing particular, everything is clear at this point.
Whereas this will be a CMS, I want to handle a Page model. A user with the nesessary permissions (mostly admin and/or root) can perform basic CRUD operations and other stuff on a page, for example:
I can create a page with the:
tile = 'About us' (this will be displayed as a headline of the page or the title of the browser tab like eg.: HTML title and h1 tags)
URL denomination = '*about_us*' (this will be the URI endpoint, like: http://domain.com/about_us)
reference name = 'Who we are' (This is the text displayed in the menubar)
page content = 'lorem ipsum...' (The actual content of the page...by a WYSIWYG html text editor)
and much more options like structuring the pages, to assign sub pages under a parent page, or making a page startpage (which means if I set 'About us' as a start page, then http://domain.com will automaticall load that page content)...
Or I can modify these properties, even I can delete a page...etc.
The MVC framework makes no difference between handling a frontend and a backend call.
For example we have some requests:
http://domain.com/user/details/121
http://domain.com/about_us
http://domain.com/our_products/1255
The first will load a backend controller as I detailed before,
but the others will load a frontend content.
When the Bootstrap loads the appropriate controller/action we look for the actual controller file, in the example above :
/controllers/Users.php
/controllers/About_us.php
/controllers/Our_products.php
The first can be loaded because that is a 'static' controller written before, but the About_us and Our_products are not existing controllers so If it is impossible to load the controller, the bootstrap searches the database if is there a page with the same URL denimination (like: about_us, our_products). If there is, we load a common FrontEndController and display the requested page data, if there isn't, display a 404 error.
I do this because I want the bootstrap to handle all requests the same way, but I dont want to every frontend URL compulsorily contain the FrontEndController (e.g.:http://domain.com/FrontEndController/our_products/1255). So this is how I hide it from the user, so the URL can remain more user friendly. My question is: Is this a good practice? Or are there any other proper ways to do this?
The MVC framework handles the request and decides what to load/call based on the URL
What you would normally is have is some sort of Router and Dispatcher class. The router would accept the the user/details/121, parse it and return a Route.
$route = $router->route( $request->getUri() );
The router could hold config values like the allowed space character in URI's, default allowed characters etc.
You can also add custom routes to the router
$router->addRoutes($routes);
The custom routes can be a simple associative array
$routes['requested-uri'] = 'custom-route'
In the example above you said when they visit the root of the website you want them to actually see the About Us page so that could be done like this:
$router->addRoutes([
'' => 'about-us
]);
Meaning when the URI is ''(blank) then go to the 'about-us' route. It shouldn't do a redirect, just transparently load up a different route while keeping the URI in the clients web browser the same.
Routing can obviously be more complex, using route objects added to a route collection for more advanced custom routing with more control. Some frameworks use annotations and all sort of different ways to achieve flexible routing.
The dispatcher could then accept the route returned from the router and dispatch it. That means verifying if the requested route actually exists i.e does the controller file exist and the requested method in the controller exist.
$view = $dispatcher->dispatch($route);
Inside the Dispatcher::Dispatch() method:
// Check if the controller file exists.
// Instantiate the controller file, preferably using a controller factory.
// Check if the controller method exists.
// Call the controller method
call_user_func_array([$controller, $route->getMethod()], $route->getParams());
$view = $controller->getView();
$action = $route->getAction();
// Call the view method.
if( method_exists($view, $action) ) {
$view->$action();
}
return $view;
I find the following a very easy to understand way of dealing with controller methods/actions. Let's say you have a login controller, the user sends a GET request to it first and a POST request to it when sending the login details in the form.
public function getIndex() { }
public function postIndex() {
$username = $this->request->post('username');
$password = $this->request->post('password');
}
The get and post in front of the method name is the request type, this prevents you having to do something like this
public function index() {
if( $this->request->getType() === 'POST' ) {
$username = $this->request->post('username');
$password = $this->request->post('password');
}
}
It also gives you more control over authorisation(if you do it at the routing layer) because you can easily allow a user to send a GET request to the controller but deny them access to sending a POST request.
Each controller has a one to one relationship with a view. The view get's injected into the controller on construction, preferably using a controller factory.
What would happen when you send a GET request http://domain.com/user/details/121 is the router would break up the URI and turn it into a route targeting the User controller, the getDetails() method with the parameter 121, the dispatcher checks if the controller and method exist, it then calls the method supplying the user ID as an argument, the controller sets the user ID in the view. Below is the User controller.
public function getDetails($userId) {
$this->getView()->setUserId( (int)$userId );
}
The view then has a method called details(). The same name as the method called in the controller, just without the request type in front of it.
The dispatcher then calls the details() method of the view which then fetches the required data.
Setting the title of the page is done in the view, as it is for presentation purposes only.
Part of the view that is related to the User controller
public function details() {
// Fetch the user by using the previously set user ID from the controller.
// If he doesn't exist set an error template, set the response code to 404,
// or redirect. Do whatever you want really.
$this->setTitle('User Details');
// Build template objects, bind the fetched user data to main template.
}
How you implement the setTitle method and all over view related stuff is up to you.
The view sends the response back to the client, whether it is HTML content, JSON, XML, or any other content type.
For example your application lets you search for users and export them to a Microsoft Excel Workbook file(.xlsx) and prompt the user to download it.
The view would:
Fetch the users
Generate the file
Set the HTTP response headers like Content-Type
Send the response
If I have a controller called articles, which has a method called view_articles, a user can type in http://example.com/articles/view_articles/some-post and have it return a page.
I have specified a route to be http://example.com/article/post-name. How can I make it so that only the URL specified in the route is visible? Is there a way for articles/view_articles/some-post to show a 404 instead of showing the same page as the route URL?
I am trying to prevent duplication for SEO purposes.
You can always make default routing to a 404 page by correctly defining routes in your routes.php file:
$routes['article/(:any)'] = 'articles/view_articles/$1';
$routes['(:any)'] = 'main/e404';
As stated by CodeIgniter user guide:
Routes will run in the order they are defined. Higher routes will always take precedence over lower ones.
So you can basically define all you want to be seen at the beginning of the file and block everything else on the last line.
As for your main(can be any other) controller's 404 method -
function e404() {
show_404();
}
Use $this->uri->segment(n) as part of the URI class inside of view_articles to redirect traffic to your route if the URI contains view_articles as it's second segment.
I'v done that in other tricky way, first of all you should add some code to __construct function in sys/core/Controller.php
you can check whether the requested url is in routes or not by this code
if(!isset($this->router->routes[uri_string()])){
show_404(); // Or whatever you want ...
}
This is my Zend Framework directory structure (only included what you'll need):
-Controllers
-HomeController.php
-IndexController.php
-Views
-scripts
-home
-index.phtml
-index
-index.phtml
In the IndexController I want to automatically redirect them to the home view.
How do I achieve this?
This is what I have tried so far:
header('/application/views/home');
I want to redirect to a different view and controller altogether. Not a different action within the same controller.
you have some options:
use the _redirect action utility method (very common)
public function indexAction() {
$this->_redirect('/application/views/home');
}
or use the _forward action utility method to just execute the requested action without actually redirecting
public function indexAction() {
$this->_forward('index', 'home');
}
you could use the action helper redirector, similar to _redirect but has many more options.
public function indexAction() {
$this->getHelper('Redirector')->gotoSimple('index','home');
}
This about covers the basics of redirecting inside of a controller, if you want to actually render home/index.phtml from /idex/index you could use the viewrenderer action helper.
I'll let you unlock those secrets.
If you want to render another action inside current action it is possible within your action the following way:
$this->render('actionName');
or, explicitly set the template to render
$this->renderScript('path/to/viewscript.phtml');
UPDATE: Reading again your question I suppose you want to use
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed.
or
_redirect($url, array $options = array()): redirect to another location. This method takes a URL and an optional set of options. By default, it performs an HTTP 302 redirect.
see documentation on this question.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the difference between redirect and forward in Zend framework
I'm new to zend framework, when i try to use foward, redirect, setViewRender in controller, what are their diffences?
As the official docs say:
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the
currently requested action will be skipped in favor of the new one.
Otherwise, after the current action is processed, the action
requested in _forward() will be executed.
_redirect($url, array $options = array()): redirect to another location. This method takes a URL and an optional set of options. By
default, it performs an HTTP 302 redirect.
Read more to understand it better.
i'm pretty sure that a redirect redirects you to a particular page/ controller therefore going through all of the pre view processes including if you can view the page, setViewRender will just output the view you have selected and disregard all of the stuff the controller etc handles. (i haven't used zend for a while however)
_redirect creates a completely new http request, where as _forward simply forwards the request. I realized this when setting a variable in zend_registry, well this might sound a little off the track but when you use _request all the request variables and headers and registry variables are completely reset where as in case of _forward it simply forwards all those things. This is like you can have previously provided information along with some more information in case of _forward.
Well for setViewRenderer I really like this idea, I mean this is something like dependency injection. You dont really need to have a default view, you could provide a new view for the given action. I think you would get the best answer if you look into this.
_forward is an internal redirect. Where as _redirect sends a header that tells the client's browser to go to some other URL, _forward tells the Dispatcher to internally redirect the request somewhere else.
If you consider the normal dispatch order of:
preDispatch()
someAction()
anotherAction()
postDispatch()
Calling _forward at any point in that progression will cause the following steps to not be executed. So if you call _forward in preDispatch(), someAction() will not be called and so on. If you _forward() in someAction() and you are using the viewRenderer action helper to render your views (you are letting the framework choose what view script to render), then no view script will be rendered in someAction().
When the request is forwarded to the new Controller / Module the entire dispatch process will be repeated there.
Note:
If you forward inside someAction() to anotherAction() you must do it like this
return $this->_forward('another');
You should add a return to $this->_forward() or your current Action will continue to execute before forwarding to the other Action.
Also note that the view that will be rendered will be another.phtml
While when doing a redirect, ZF would instruct the browser to load http://example.com/controller-name/action-name as _redirect() sends a header, meaning you create a new HTTP Request and go through the entire dispatch process with it.
Last to render a certain view inside an action use the viewRenderer Action helper:
// Bar controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
public function addAction()
{
// Render 'bar/form.phtml' instead of 'bar/add.phtml'
$this->_helper->viewRenderer('form');
}
public function editAction()
{
// Render 'bar/form.phtml' instead of 'bar/edit.phtml'
$this->_helper->viewRenderer->setScriptAction('form');
}
public function processAction()
{
// do some validation...
if (!$valid) {
// Render 'bar/form.phtml' instead of 'bar/process.phtml'
$this->_helper->viewRenderer->setRender('form');
return;
}
// otherwise continue processing...
}
}