I am busy learning as much as I can about php frameworks.
At the moment I have a file full of routes. The routes take this general form.
$bootstrap->router->add('/home', ['controller' => 'home', 'action' => 'index']);
So, the 'routes' file is not a class, it is just a file which I include into the index.php page just underneath $bootstrap = new Bootstrap($router);.
As you can see, to add a route I add it to the instantiated $bootstrap object, which itself has an instantiated ->router object inside which I am calling the 'add()' method.
Now I want to make it easier to add routes by writing something like this...
route('/home', ['controller' => 'home', 'action' => 'index']);
question
Exactly how to go about this is unclear... I am not even sure if this is a done thing. I have tried making a 'helpers/RoutesHelper file' but that takes my request out of the object scope...
...so I am a bit confused, how do I go about implementing a plane old helper function inside object oriented code?
Many thanks in advance.
anonymous function
If you are willing to add a $ to the beginning of that route(... line:
$route = function($path, $options) use ($bootstrap) {
$bootstrap->router->add($path, $options);
}
would work.
global $bootstrap
Otherwise it would be dirty to bring the bootstrap object into the route function:
function route($path, $options) {
global $bootstrap; // <-- i don't like this
$bootstrap->router->add($path, $options);
}
definitions in array
you could also just make an array in your file like:
$routes = array()
$routes['/home'] = ['controller' => 'home', 'action' => 'index'];
// ... more routes ...
and in your main program just loop over that array:
foreach($routes as $path => $options) {
$bootstrap->router->add($path, $options);
}
those are the three solutions that pop into my mind ...
Like this
if(!function_exists( 'route' ) ){
function route( $path, $params ){
$bootstrap = ''; //?
$bootstrap->router->add($path, $params);
}
}
Of course I have no idea where $bootstrap came from, so you'll want that in there, obviously. You could pass that in as a parameter, but it would be better to initialize it inside the function if possible.
Related
Hi I'm new to the php world.
I'm wondering What is the best way to handle multilingual routing ?
I'm starting to create a website with Phalcon php.
I have the following routing structure.
$router->add('/{language:[a-z]{2}}/:controller/:action/:params', array(
'controller' => 2,
'action' => 3,
'params' => 4,
));
$router->add('/{language:[a-z]{2}}/:controller/:action', array(
'controller' => 2,
'action' => 3,
));
$router->add('/{language:[a-z]{2}}/:controller', array(
'controller' => 2,
'action' => 'index',
));
$router->add('/{language:[a-z]{2}}', array(
'controller' => 'index',
'action' => 'index',
));
My problem is for instance when I go on mywebsite.com/ I want to change my url in the dispatcher like mywebsite.com/en/ or other language. Is it a good practise to handle it in the beforeDispatchLoop ? I seek the best solutions.
/**Triggered before entering in the dispatch loop.
* At this point the dispatcher don't know if the controller or the actions to be executed exist.
* The Dispatcher only knows the information passed by the Router.
* #param Event $event
* #param Dispatcher $dispatcher
*/
public function beforeDispatchLoop(Event $event, Dispatcher $dispatcher)
{
//$params = $dispatcher->getParams();
$params = array('language' => 'en');
$dispatcher->setParams($params);
return $dispatcher;
}
This code doesn't work at all, my url is not change. The url stay mywebsite.com/ and not mywebsite.com/en/
Thanks in advance.
I try one solution above.
The redirect doesn't seems to work. I even try to hard-coded it for test.
use Phalcon\Http\Response;
//Obtain the standard eventsManager from the DI
$eventsManager = $di->getShared('eventsManager');
$dispatcher = new Phalcon\Mvc\Dispatcher();
$eventsManager->attach("dispatch:beforeDispatchLoop",function($event, $dispatcher)
{
$dispatcher->getDI->get('response')->redirect('/name/en/index/index/');
}
I think you are confusing something. If I understand correctly: you want to redirect users to a valid url if they open a page without specifying a language.
If that's the case you should verify in your event handler whether the language parameter is specified and redirect user to the same url + the default language if its missing. I am also assuming that your beforeDispatchLoop is located in your controller, which is also part of the problem, because your route without a language never matches and you never get into that controller. Instead you need to use it as an event handler with the event manager as per the documentation. Here is how you do the whole thing.
$di->get('eventsManager')->attach("dispatch:beforeDispatchLoop", function(Event $event, Dispatcher $dispatcher)
{
$params = $dispatcher->getParams();
// Be careful here, if it matches a valid route without a language it may go into a massive
// recursion. Really, you probably want to use `beforeExecuteRoute` event or explicitly
// check if `$_SERVER['REQUEST_URI']` contains the language part before redirecting here.
if (!isset($params['language']) {
$dispatcher->getDI->get('response')->redirect('/en' . $_SERVER['REQUEST_URI']);
return false;
}
return $dispatcher;
});
i am new comer for cakephp framework. i can not call functions of controller.
Controller-
class PagesController extends AppController {
public $name = 'Pages';
public $uses = array();
public function display() {
$path = func_get_args();
$count = count($path);
if (!$count) {
$this->redirect('/');
}
$page = $subpage = $title_for_layout = null;
if (!empty($path[0])) {
$page = $path[0];
}
if (!empty($path[1])) {
$subpage = $path[1];
}
if (!empty($path[$count - 1])) {
$title_for_layout = Inflector::humanize($path[$count - 1]);
}
$this->set(compact('page', 'subpage', 'title_for_layout'));
$this->render(implode('/', $path));
}
public function register() {
$this->set('fdf', 'chandan');
$this->render('home1');
}
}
But i am calling display(). but i am not calling register(). my routes.php file like-
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Please help me. how to call controller function from view in cakephp.
and what setting have to done for it ?.
A few points I would make, the routes file is for defining custom slugs/url, take a look at your first route definition here:
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
This is saying that "www.mysite.com/" should link to the controller pages, the action display and pass the first parameter as home.
This can be accessed by doing "www.mysite.com/pages/display/home" in short - but using "/" as a route is tidier. The general rule is "www.mysite.com/controller/action/param1/param2/etc.."
So following this logic you would access your new action method like such:
"www.mysite.com/pages/register"
That being said... When using MVC you should really follow the conventions set out, if you're going to create a register method you should really contain it within a controller which deals with user accounts i.e. "UsersController" - "www.mysite.com/users/register"
Also, you shouldn't really need to use $this->render() unless you have to render a separate view under special conditions.
To sum up, contain all actions within a relevant controller (i.e. www.mysite.com/users/login and www.mysite.com/users/register), never directly specify $this->render unless you really need to render something other than the default (/users/register.ctp would be the default for www.mysite.com/users/register) and routes are used to create tidier or custom urls.
I would highly recommend you read and follow the blog tutorial to grasp these concepts.
In my controller I create the Navigation object and passing it to the view
$navigation = new \Zend\Navigation\Navigation(array(
array(
'label' => 'Album',
'controller' => 'album',
'action' => 'index',
'route' => 'album',
),
));
There trying to use it
<?php echo $this->navigation($this->navigation)->menu() ?>
And get the error:
Fatal error: Zend\Navigation\Exception\DomainException: Zend\Navigation\Page\Mvc::getHref cannot execute as no Zend\Mvc\Router\RouteStackInterface instance is composed in Zend\View\Helper\Navigation\AbstractHelper.php on line 471
But navigation which I use in layout, so as it is written here: http://adam.lundrigan.ca/2012/07/quick-and-dirty-zf2-zend-navigation/ works. What is my mistake?
Thank you.
The problem is a missing Router (or to be more precise, a Zend\Mvc\Router\RouteStackInterface). A route stack is a collection of routes and can use a route name to turn that into an url. Basically it accepts a route name and creates an url for you:
$url = $routeStack->assemble('my/route');
This happens inside the MVC Pages of Zend\Navigation too. The page has a route parameter and when there is a router available, the page assembles it's own url (or in Zend\Navigation terms, an href). If you do not provide the router, it cannot assemble the route and thus throws an exception.
You must inject the router in every page of the navigation:
$navigation = new Navigation($config);
$router = $serviceLocator->get('router');
function injectRouter($navigation, $router) {
foreach ($navigation->getPages() as $page) {
if ($page instanceof MvcPage) {
$page->setRouter($router);
}
if ($page->hasPages()) {
injectRouter($page, $router);
}
}
}
As you see it is a recursive function, injecting the router into every page. Tedious! Therefore there is a factory to do this for you. There are four simple steps to make this happen.
STEP ONE
Put the navigation configuration in your module configuration first. Just as you have a default navigation, you can create a second one secondary.
'navigation' => array(
'secondary' => array(
'page-1' => array(
'label' => 'First page',
'route' => 'route-1'
),
'page-2' => array(
'label' => 'Second page',
'route' => 'route-2'
),
),
),
You have routes to your first page (route-1) and second page (route-2).
STEP TWO
A factory will convert this into a navigation object structure, you need to create a class for that first. Create a file SecondaryNavigationFactory.php in your MyModule/Navigation/Service directory.
namespace MyModule\Navigation\Service;
use Zend\Navigation\Service\DefaultNavigationFactory;
class SecondaryNavigationFactory extends DefaultNavigationFactory
{
protected function getName()
{
return 'secondary';
}
}
See I put the name secondary here, which is the same as your navigation key.
STEP THREE
You must register this factory to the service manager. Then the factory can do it's work and turn the configuration file into a Zend\Navigation object. You can do this in your module.config.php:
'service_manager' => array(
'factories' => array(
'secondary_navigation' => 'MyModule\Navigation\Service\SecondaryNavigationFactory'
),
)
See I made a service secondary_navigation here, where the factory will return a Zend\Navigation instance then. If you do now $sm->get('secondary_navigation') you will see that is a Zend\Navigation\Navigation object.
STEP FOUR
Tell the view helper to use this navigation and not the default one. The navigation view helper accepts a "navigation" parameter where you can state which navigation you want. In this case, the service manager has a service secondary_navigation and that is the one we need.
<?= $this->navigation('secondary_navigation')->menu() ?>
Now you will have the navigation secondary used in this view helper.
Disclosure: this answer is the same as I gave on this question: https://stackoverflow.com/a/12973806/434223
btw. you don't need to define controller and action if you define a route, only if your route is generic and controller/action are variable segments.
The problem is indeed that the routes can't be resolved without the router. I would expect the navigation class to solve that issue, but obviously you have to do it on your own. I just wrote a view helper to introduce the router with the MVC pages.
Here's how I use it within the view:
$navigation = $this->navigation();
$navigation->addPage(
array(
'route' => 'language',
'label' => 'language.list.nav'
)
);
$this->registerNavigationRouter($navigation);
echo $navigation->menu()->render();
The view helper:
<?php
namespace JarJar\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\View\Helper\Navigation;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Navigation\Page\Mvc;
class RegisterNavigationRouter extends AbstractHelper implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function __invoke(Navigation $navigation)
{
$router = $this->getRouter();
foreach ($navigation->getPages() as $page) {
if ($page instanceof Mvc) {
$page->setRouter($router);
}
}
}
protected function getRouter()
{
$router = $this->getServiceLocator()->getServiceLocator()->get('router');
return $router;
}
}
Don't forget to add the view helper in your config as invokable instance:
'view_helpers' => array(
'invokables' => array(
'registerNavigationRouter' => 'JarJar\View\Helper\RegisterNavigationRouter'
)
),
It's not a great solution, but it works.
I am experiencing some difficulty setting up the functionality for an admin to edit an items values. I have created the editAction() function in the AdminItemController class. This is contained within a module called catalog. My routing is configured as the following:
resources.router.routes.admin-catalog-edit.route = "/admin/catalog/item/edit/:id"
resources.router.routes.admin-catalog-edit.defaults.module = "catalog"
resources.router.routes.admin-catalog-edit.defaults.controller = "admin.item"
resources.router.routes.admin-catalog-edit.defaults.action = "edit"
I have created a custom Zend_Form class and within this class I set the action and method for the form:
class My_Form_ItemAdd extends Zend_Form
{
public function init()
{
$this->setAction('/admin/catalog/item/edit')
->setMethod('post');
...
Within my controller action I have instantiated the form and pass it to the view to be rendered. I also test if it's a POST (if so validate and save to database), otherwise, test for GET (if so, extract ID and populate()):
class Catalog_AdminItemController extends Zend_Controller_Action
{
...
public function editAction()
{
$form = new My_Form_ItemEdit();
$this->view->form = $form;
...
The form loads just fine in the browser when I supply an ID at the end for GET request... however, when I submit the form an exception is thrown with the following request parameters:
array (
'controller' => 'admin',
'action' => 'catalog',
'item' => 'edit',
'module' => 'default',
...
I have no idea why the it would be doing this... is there something I'm not seeing??? Any advice would be much appreciated!
The problem lies in your route. The default behavior for /admin/catalog/item/edit/:id is to process it like /controller/action/:param/:param/:param which puts both item and edit as parameters instead of your intended purpose. Try adding something like this to your bootstrap:
protected function _initRoutes()
{
// Get front controller
$front = Zend_Controller_Front::getInstance();
// Get router
$router = $front->getRouter();
// Add route
$router->addRoute(
'admin_item_edit',
new Zend_Controller_Router_Route('admin/catalog/item/edit/:id',
array('controller' => 'item',
'action' => 'edit'))
);
}
This allows you to define the specific controller and action from the route.
I read the tutorial, and found that to use the "admin" prefix, you can just uncomment the:
Configure::write('Routing.prefixes', array('admin'));
config/core.php file.
I did that, and my admin routing works great - /admin/users/add hits the admin_add() function in my users_controller.
The problem is - it's also changing my normal links - ie. my "LOGOUT" button now tries to go to /admin/users/logout instead of just /users/logout. I realize I can add 'admin'=>false, but I'd rather not have to do that for every link in my site.
Is there a way to make it so ONLY urls with either 'admin'=>true or /admin/... to go to the admin, and NOT all the rest of the links?
Expanding on user Abba Bryant's edit, have a look at how to create a helper in the cook book : http://book.cakephp.org/view/1097/Creating-Helpers
If having to disable routing manually on all your links is an annoyance (it would be to me!), you could create a new helper MyCustomUrlHelper (name doesn't have to be that long of course), and have it use the core UrlHelper to generate the URLs for you.
class MyCustomUrlHelper extends AppHelper {
public $helpers = array('Html');
function url($controller, $action, $params ,$routing = false, $plugin = false) {
//Example only, the params you send could be anything
$opts = array(
'controller' => $controller,
'action' => $action
//....
);
}
//another option
function url($params) {
//Example only, the params you send could be anything
$opts = array(
'controller' => $params['controller'],
'action' => $params['action']
//....
)
}
//just fill up $opts array with the parameters that core URL helper
//expects. This allows you to specify your own app specific defaults
return $this->Html->url($opts); //finally just use the normal url helper
}
Basically you can make it as verbose or terse as you want. It's just a wrapper class for the the actual URL helper which will do the work from inside. This allows you to give defaults that work for your specific application. This would also allow you to make a change in one place and have the routing for the whole application be updated.
EDIT
You could also check whether the passed $opts array is a string. This way you can have the best of both worlds.
Make sure if you use the prefix routing that you handle it in the HtmlHelper::link calls like so
<?php
...
echo $html->link( array(
'controller' => 'users',
'action' => 'logout',
'plugin' => false,
'admin' => false,
));
...
?>
** EDIT **
You could extend the url function in your AppHelper to inspect the passed array and set the Routing.prefixes keys to false if they aren't already set in the url call.
You would then need to specify the prefix in your admin links every time.
The HtmlHelper accepts two ways of giving the URL: it can be a Cake-relative URL or an array of URL parameters.
If you use the URL parameters, by default if you don't specify the 'admin' => false parameter the HtmlHelper automatically prefixes the action by 'admin' if you are on an admin action.
IMHO, the easiest way to get rid off this parameter is to use the Cake-relative URL as a string.
<?php
//instead of using
//echo $this->Html->link(__('logout', true), array('controller' => 'users', 'action' => 'logout'));
//use
echo $this->Html->link(__('logout', true), '/users/logout');
Kind regards,
nIcO
I encountered this problem this week and this code seemed to fix it. Let me know if it doesn't work for you and I can try to find out what else I did to get it to work.
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'logout');
$this->Auth->loginRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'welcome');
This was really frustrating, so I'm glad to help out.
I'm late to the party, but I have a very good answer:
You can override the default behavior by creating an AppHelper class. Create app/app_helper.php and paste the following:
<?php
class AppHelper extends Helper{
function url($url = null, $full = false) {
if(is_array($url) && !isset($url['admin'])){
$url['admin'] = false;
}
return parent::url($url, $full);
}
}
?>
Unless it is specified when you call link() or url(), admin will be set to false.