I've just put together a very basic site using the Zend Framework and its MVC. (Actually, I'm not even using models at the moment, it's all just Controllers/Views for static info so far).
When I started toying with the Forms I realized that in the examples for Zend_Form they use something like this this set the form's action:
$form->setAction('/user/login')
Which contains the URL. I understand that Zend Framework has Routes and that they can be named, but I can't seem to grasp from the manual how to create a simple route for certain Controller/Actions so that I could do something like this:
$form->setAction($named_route)
// or
$form->setAction('named_route')
I hope my question is clear. I wasn't able to locate any duplicate questions, but if you spot one and point it out I won't mind.
Links to resources are as good as examples, so don't waste your time if you know of a decent blog post somewhere. Thanks!
References:
http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.standard - Look for "12.5.7.1. Zend_Controller_Router_Route" for a clearer explanation.
This is not the best way to do it, but I have a working example. I welcome corrections.
After seeing Shorten Zend Framework Route Definitions, I agree that named Routes should go in their own config (I use Django, and named Views/URLs are generally separated) - but here I'm just going to define Routes in my Bootstrap.
So, in Bootstrap.php, inside the Bootstrap Class of course, I've created an function that will be automatically run, like so:
public function _initRoutes()
{
$frontController = Zend_Controller_Front::getInstance();
$route = new Zend_Controller_Router_Route(
'login/', // The URL, after the baseUrl, with no params.
array(
'controller' => 'login', // The controller to point to.
'action' => 'index' // The action to point to, in said Controller.
)
);
$frontController->getRouter()->addRoute('loginpage', $route);
}
In the above example, "loginpage" will be the Name of the "Named Route".
So, inside my LoginController, (in a function that builds the form) instead of doing
$form->setAction('/blah/login')
I retrieve the URL of the named Route and pass that in, like so:
$form_action_url = $this->view->Url(array(), 'loginpage', true);
// -- SNIP --
$form->setAction($form_action_url) // ...
This may be pointless and wrong, but it seems to work at the moment.
My reason for wanting a named URL, when Zend Framework handles URLs as /Controller/View(Action)/ automatically is because I'm anal about that kind of thing. I've been using Django for awhile, where the URLs are predefined, and I like it that way.
The Zend Framework MVC urls working out of the box is nice, tho.
Feel free to add notes and corrections to how this should work!
Related
I am trying to use Symfony to replicate behavior in an existing Framework (zikula). This framework is extensible using modules which are basically extended symphony bundles. The old framework had urls like so
index.php?module=foo&type=bar&func=zip
which in symfony speak roughly translates to
index.php?bundle=foo&controller=bar&method=zip
The framework has an AbstractController which has a magic method like:
public function __call($method, $args)
{
$event = new \Zikula\Core\Event\GenericEvent($this, array('method' => $method, 'args' => $args));
$this->eventManager->dispatch('controller.method_not_found', $event);
if ($event->isPropagationStopped()) {
return $event->getData();
}
}
so, if you created a url with a method that didn't exist in the bundle, you could create a listener to capture it and send a response that looks like and behaves like it came from the specified bundle. We use this to call module services that are available to all modules and provided in a separate module but look like they are served by the 'host' module.
Now I am trying to replicates this using symfony and routing.
the first problem is generating a route that doesn't technically exist. Is this possible?
The second problem is capturing the RouteNotFoundException (which I know how to do, we already have listeners for other exceptions).
The last problem is making it appear that the bundle is serving up the response when it is actually being served by an event listener (or something else). This last part is important because other content in the response needs to come from the module/bundle.
I have tried changing the current listener to a controller, and also tried adding a method to our extension of symfony's AbstractController, but haven't yet achieved what I am hoping to achieve. I'm hoping for some suggestions on new ideas or methods to try.
I gave up trying to replicate the exact behavior as it seems impossible (it is also pretty difficult to describe). So I have resorted to a normal controller with standard route, but I found a way to make it appear to belong to the original 'host' module. Thanks to Gerry, ggioffreda and DerStoffel for offering ideas.
I've trying to expand my knowledge on separation of concerns in php. I've been practicing for over a year I think and trying to write my own mvc framework for practice. Now I am stuck again in routing and on how to initiate MVC triad.
I have this uri that I want to map so I can identify which controller and which view to use
$uri = filter_var(rtrim(filter_input(INPUT_GET, 'url', FILTER_SANITIZE_STRING), '/'), FILTER_SANITIZE_URL);
lets say this piece of code resides in my bootstrap.php file that acts as the entry point.
While reading Tom Butler's Blog I realize many things, like views should have access to models, but not quite, using a viewmodel is better, or just a model.
I've come across his IOC or his Dependency Injection Container and fell interested on trying it.
What lacks in that article is the dispatching part, which I am very interested to learn, I've tried a couple things to make it work but with no avail.
I wanted to implement this because I want a single call of controller can have shared dependency across its views, something like
$route = $router->getRoutes(); // maybe something that return a Route object with Controller, and View object that has already shared dependecies.
I do not know if my understanding on the above paragraphs where correct and if really can be of use on my routing. Correct me if I am wrong.
The real question is how would the dispatcher looks like? and if I were to use the convention over configuration stuff of mr. tom, should I declare the routes individually in my bootstrap? Like these
$dice->addRule('$route_user/edit', $rule);
$dice->addRule('$route_user/delete', $rule);
...
I wonder if I could just do:
$controller->method($params)
After I have settled on what view and controller I needed.
I'm not sure that this answer will be what you want, but if I where you I'll do it this way, it will be more restFull, and more "convention over configuration":
Use http verbs for edit , delete...
Use a standard way to handle your urls: "article/", "article/2"
Where article is your controller name AND your view name
Use a simple array tree for simple routes:
$bootstrap=[
...
"routes"=>[
"myFirstCategory"=>[
"art"=>"article",
...
],
"mySecondCategory"=>[
...
This way, myFirstCategory/art can be redirected to article controller and view, with a recursive function that handle the routes tree
In this tree you can use a callback rule for complex rules (that callback have to be handeled by your recursive function for the routes tree:
... "art"=>function($myContainer){...return ['view'=>$view,'controller'=>$controller,'id'=>$id...];}...
It's just some ideas to make it more easy to use...
I'm currently looking into CakePHP 2.0 and wanting to convert old 1.3 projects to 2.0. I'm going to start from scratch because there's a whole lot of code in the projects that could be a lot better.
One of those things is the dynamic URLs, the projects multilingual and even the URLs change to the chosen language. Eg:
English: /pages/new-article
Dutch: /paginas/nieuw-artikel
Both would go to PagesController::display();
Note: the URLs can be way longer, pages can have subpages and those will be added to the URL too. Eg: /pages/new-article/article-subpage
Now, the way I did it before is to have a route for everything going to a specific action. Like * going to PagesController::index();
However this seems to slow the apps down and it brings a lot of problems along with it.
So my question to you is, is there a simpler way to do this?
I do not want to hardcode anything, I should be able to change /pages/article to /page/article without needing to change the code.
Note: If you know a way to do it in 1.2 or 1.3, that would also be great, 2.0 isn't that different.
Well i figured it out, apparently CakePHP 1.3 and 2.0 allow you to create custom route classes. It's in the documentation here: http://book.cakephp.org/2.0/en/development/routing.html?highlight=route#custom-route-classes
So basically what you need to do is create a file APP/Lib/Routing/Route/UrlRoute.php with the following contents:
class UrlRoute extends CakeRoute{
public function parse($url){
$params = parent::parse($url);
# Here you get the controller and action from a database.
// tmp
$params['controller'] = 'pages';
$params['action'] = 'index';
return $params;
}
}
And in your APP/Config/routes.php you put the following:
App::import('Lib', 'Routing/Route/UrlRoute');
Router::connect('/*', array('controller' => 'tests', 'action' => 'index'), array('routeClass' => 'UrlRoute'));
I think the real challenge is getting the arguments that usually get passed to the functions back to work. func_get_args() now returns everything behind the domain name. And retrieving the URL from the database if you're using extra params. Might have to cache each URL.
Let's pretend I'm trying to learn CI, and as my test project I am building a group-buying site.
What I'd like is to have a different page for each city, e.g.:
http://www.groupon.com/las-vegas/
http://www.groupon.com/orlando/
I'd also like to have different pages such as:
http://www.groupon.com/learn
http://www.groupon.com/contact-us
If I am building this in CI and following the MVC ideology, how would this work? I'm having difficulty seeing how to accomplish the desired URL's with the concept of:
http://www.domain.com/controller/view/segment_a/segment_b/etc...
What I would do is create a custom 404 controller that acts as a catch-all for non-existent routes.
It would take the URI, possibly validate it, and re-route it to the (e.g.) "city" controller.
If the city controller can't find the city (whatever string was specified), then it needs to issue a proper 404. Otherwise, you're good to display your information for that city.
Also, once you create your custom 404 controller, you can send all 404 errors to it by specifying a route named '404_override'.
That's where URI Routing comes in. But in your case you'll probably will have to be carefull defining your routes as the first and only part of your route is a variable part already.
This really has nothing to do with MVC, and much more to do with good URL.
You're looking for URLs that are both (a) clear from the user's point of view and (b) that give hints to your application as to how it's meant to be handled.
What I'd do in this case is redesign your URLs slightly so that rather than:
http://www.groupon.com/las-vegas/
http://www.groupon.com/orlando/
You would have URLs that looks like this:
http://www.groupon.com/destinations/las-vegas/
http://www.groupon.com/destinations/orlando/
The bit at the beginning--/destinations/--can be used by your URL routing code to decide what controller should be dealing with it. If your routing code is URL-based, you might have an array like this:
$routes = array(
'/destinations/' => 'on_destination_list',
'/destinations/(.+)' => 'on_destination',
'/(.*)' => 'on_page');
// Basic URI routing code based off of REQUEST_URI
foreach ($pattern => $func) {
if (preg_match("`^$pattern$`", $_SERVER['REQUEST_URI'], $placeholders)) {
array_shift($placeholders);
call_user_func($func, $placeholders);
}
}
Keep in mind that I wrote that routing code off the top of my head and it may not be absolutely correct. It should give you the gist of what you need to do.
Doing things this way has the added benefit that if somebody goes to http://www.groupon.com/destinations/, you'll have the opportunity to show a list of destinations.
I am working on a project that needs to use a database driven MVC scheme where the route to the controllers and views are controlled through a single database table. However, I haven't been able to find any tutorials that demonstrate this with a current version of the framework (they all appear to have been written several versions ago) and I was wondering if anyone has done something like this with a more recent version of the framework or if anyone knows of blogs or tutorials that discuss how to accomplish this in a simple manner.
The basic idea is that there will be a sitePage table that will contain pageName, controller, module and view fields. When the request is processed I need to query the database for the given pageName and determine the appropriate controller, module and view and then pass this into the necessary Zend class to continue with the normal routing and processing of the request.
Thanks in advance.
You can also use the routeStartup() method in your plugin.
eg:
class My_Plugin_PageRoute extends Zend_Controller_Plugin_Abstract {
public function routeStartup () {
$front = Zend_Controller_Front::getInstance();
$pages = new Model_Pages();
$page_data = $pages ->getPageInfo();
$router = $front->getRouter();
foreach($page_data as $page) {
$r = new Zend_Controller_Router_Route(
'' . $page -> page_name,
array('controller' => 'pages',
'action' => 'index',
'page_id' => $page -> page_id)
);
$router->addRoute('pages_' . $page -> page_id, $r);
}
}
}
I realized that a more elegant approach is indeed to use a router, but for that you would need to create a custom one by extending the Zend_Controller_Router_Abstract class and implementing the "route" method.
You get a Zend_Controller_Request_Abstract object as the parameter of the "route" method. There you can talk to the database and then you can use:
Zend_Controller_Request_Abstract::setModuleName(),
Zend_Controller_Request_Abstract::setControllerName(),
Zend_Controller_Request_Abstract::setActionName()
to define your route.
I hope it helps!
Maybe the best aproach is not by using routers but by using plugins or a common controller. Without a deeper analysis I would suggest you to create a Front Controller Plugin, and then inside the preDispatch() method you can talk to the database and reset the request so it is dispatched to the right controller.
You can also get the same effect by using a common controller, all requests are routed to it then it can forwards to the right controller after talking to the database, although I prefer to use a plugin.
From the Manual:
preDispatch() is called before an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag (via Zend_Controller_Request_Abstract::setDispatched(false)), the current action may be skipped and/or replaced.
http://framework.zend.com/manual/en/zend.controller.plugins.html