I want want dispatch another controller action when Zf2 raise route not found exception to display my custom page ( i dont want to display a custom error page). I am working on dynamic url from database in Zf2 which will occur only when route not found.
i added a event in bootstrap function
$event->attach('route', array($this, 'loadConfiguration'), 2);
in loadConfiguration function i added to load
public function loadConfiguration(MvcEvent $e){
$application = $e->getApplication();
$sm = $application->getServiceManager();
$router = $sm->get('router');
$request = $sm->get('request');
$matchedRoute = $router->match($request);
if (null == $matchedRoute) {
$request_uri = $e->getRequest()->getrequestUri();
$dbAdaptor = $e->getApplication()->getServiceManager()->get('Zend\Db\Adapter\Adapter');
$url_table = new UrlMappingTable($dbAdaptor);
$url_data = $url_table->find($request_uri);
$controller = $url_data['controller'];
$action = $url_data['action'];
$id = $url_data['post_id'];
$original_url = $url_data['original_url'];
$alias = $sm->get('Application\Router\Alias');
$alias->setNavigation($original_url);
if(isset($url_data)){
$url = $e->getRouter ()->assemble (array('controller' => $controller,
'action' => $action ,
'id' => $id,
), array (
'name' => 'myurl'
) );
}
print 'no route match';
}
}
after getting the controller and action i just want the dispatcher to forward this controller.
I needed something similar for my project. I ended up just adding a 'catchall' rule in module.config.php
i.e.
'router' => array(
'routes' => array(
'catchAll' => array(
'type' => 'regex',
'options' => array(
'regex' => '/(?<page>.+)',
'defaults' => array(
'controller' => 'Project\Controller\MyController',
'action' => 'customPage',
),
'spec' => '/%page%',
),
),
//Other route items ...
),
//Other stuff ...
)
Place this as the first item in the routes array so it has the lowest precedence. Then you can have your customePageAction to do whatever you want!
Just something not really answering your question:
I am working on dynamic url from database in Zf2 which will occur only when route not found.
There are two ways you can achieve this much more efficiently. If there aren't much routes, you can load them before the route event. For example, you query on bootstrap all your database routes and inject them into the route stack.
Another way is creating a "catch all" route which always will match after all routes failed. Then you don't have a "route not found" but your default route is matched. This will then look for a matching database record. If none found, you just return a 404 response.
In case #1, the controller mapped by your database route is directly dispatched. In the second case, you are in your "database controller" and want to dispatch your controller mapped by the database route, you use the forward plugin:
public function matchAction()
{
// Fetch route from db here
if (!$match) {
$this->getResponse()->setStatusCode(404);
return;
}
return $this->forward($match->getController, array(
'action' => $match->getAction()
));
}
Related
I am new to Zend & working on Zend2, I have cron job functionality to make some automatic notifications. For this the functionality is ready & it was set up in Cron (Linux server).
Now when ever a call is made to these functions they are getting redirected to Login action. Now I should allow these specific notification functions to get rid of this authentication process.
In cakephp we have $this->Auth->allow('index') which allows to work without login action. Is there a way to do similar to this in zend 2?
I've a link similar to this. But it doesn't say where to mention the action name in the ACL
This is the way I do it, using custom acl management code:
In my User module, I place the Acl config to manage the access to the resources for 3 differents roles: guest, member, admin.
The module.config.php has the following attribute "acl":
'acl' => array(
'role' => array(
'guest' => null,
'member' => array('guest'),
'admin' => null,
),
// List of modules to apply the ACL. This is how we can specify if we have to protect the pages in our current module.
'modules' => array(
'User',
'Application'
),
'resource_aliases' => array(
'User\Controller\Account' => 'account',
...
),
'resource' => array(
// resource -> single parent
'account' => null,
'log' => null
...
),
'allow' => array(
array('guest', 'log', array('in', 'out')),
array('guest', 'account', array('register', 'verify', 'recovery', 'verificationprogress')),
...
array('admin', null, null), // the admin can do anything with the accounts
),
'deny' => array(
),
'defaults' => array(
'guest_role' => 'guest',
'member_role' => 'member',
'admin_role' => 'admin',
),
)
In the onBootstrap method of my Module.php:
...
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, 'protectPage'), -100);
...
The protectPage function looks like this:
public function protectPage(MvcEvent $event) {
$match = $event->getRouteMatch();
if (!$match) {
//onDispatchError do the job
}
$controller = $match->getParam('controller');
$action = $match->getParam('action');
$namespace = $match->getParam('__NAMESPACE__');
$parts = explode('\\', $namespace);
$moduleNamespace = $parts[0];
$services = $event->getApplication()->getServiceManager();
$config = $services->get('config');
// check if the current module wants to use the ACL
$aclModules = $config['acl']['modules'];
if (!empty($aclModules) && !in_array($moduleNamespace, $aclModules)) {
return;
}
$auth = $services->get('auth');
$acl = $services->get('acl');
// get the role of the current user
$session = new Container("appData");
$role = "guest";
if (isset($session->user->role))
$role = $session->user->role;
// Get the short name of the controller and use it as resource name
// Example: User\Controller\Course -> course
$resourceAliases = $config['acl']['resource_aliases'];
if (isset($resourceAliases[$controller])) {
$resource = $resourceAliases[$controller];
} else {
$resource = strtolower(substr($controller, strrpos($controller, '\\') + 1));
}
// If a resource is not in the ACL add it
if (!$acl->hasResource($resource)) {
$acl->addResource($resource);
}
try {
//if the role is allow to pass
if ($acl->isAllowed($role, $resource, $action)) {
//do whatever you need since the use is allowed to access this resource
}else{
//send the user to log/in resource
}
} catch (AclException $ex) {
// #todo: log in the warning log the missing resource
}
}
I hope it helps.
I am using CakePHP 1.3 and have some troubles with prefix routing.
I configured routes like that:
Router::connect(
'/listing/*',
array(
'controller' => 'dsc_dates',
'action' => 'listing',
)
);
Router::connect(
'/modular/listing/*',
array(
'controller' => 'dsc_dates',
'action' => 'listing',
'prefix' => 'modular'
)
);
in my controller there are two functions:
function modular_listing($order = null,$orderDirection = null, $items=null, $location_id=null) {
$this->layout='module';
$this->setAction('listing',$order, $orderDirection, $items, $location_id);
}
function listing($order = null,$orderDirection = null, $items=null, $location_id=null){...}
The prefix action should just change some things and then operate like the normal 'listing' method. Until here it works fine.
But if i create relative links (with HTML Helper) Router::url() uses 'modular_listing' as action which does not fit into my routes. It should be 'listing' instead of 'modular_listing'.
The controller params are correct with 'listing' as action but the router params still says 'modular_listing'.
So relative links:
$this->Html->link('example',array('parameter'));
will end up in:
/dsc_dates/modular_listing/parameter
How can I get the correct links so that the router uses 'listing' as action?
UPDATE:
It is not an alternative to add 'controller' and 'action' to the url array of the link generation. In fact I have problems with the automatically generated relative links from the paginator.
I couldn't tell if you wanted the generated Html->link() routes with the leading controller or not, so I did both:
Controller (note the renderer):
// DscDatesController.php
public function listing($param = null) {
$this->set('param', $param);
$this->render('listing');
}
public function modular_listing($param = null) {
//
$this->setAction('listing', $param);
}
Routes:
// routes.php
Router::connect(
// notice no leading DS
'listing/*',
array(
'controller' => 'DscDates',
'action' => 'listing'
)
);
Router::connect(
'/modular/listing/*',
array(
'controller' => 'DscDates',
'action' => 'listing'
)
);
View:
// DscDates/listing.ctp
<?php
// generates /dsc_dates/listing/:param
echo $this->Html->link(
'example',
array('controller'=>'dsc_dates', 'action'=>'listing', $param));
// generates /listing/:param
echo $this->Html->link(
'example',
array('action'=>'listing', $param));
About wildcards, DS and routing order:
CakePHP broken index method
HTH :)
I've got two or more routes that will be going to the same controller and action. This is fine until I want to use a helper such as the form helper or pagination on the page.
What happens is that the current url changes to whatever is declared first in my routes.php file.
I see there is a way to promote a router with Router::promote but I'm not sure if I can do it based on the current url or router being used or if there's a bett way to do this.
Here's an example of what my router.php looks like:
Router::connect('/cars-for-sale/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/new-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Router::connect('/used-cars/results/*', array('controller' => 'listings', 'action' => 'results'));
Let's say for example that I'm at the url domain.com/used-cars/results/ and I'm using the form helper or pagination helper, the url that is being put in the action or href is domain.com/cars-for-sale/results/.
Any help or info would be appreciated.
Routes should be unique and identifiable!
The problem with these Routes is that, basically, you created duplicate URLs not only does this cause problems with CakePHP picking the right route, Google doesn't like that as well; duplicated content will have a negative effect on your SEO ranking!
In order to pick the right URL (Route), CakePHP should be able to do so, based on its parameters; your current Routes do not offer any way to distinguish them.
And neither does your application!
All these URLs will present the same data;
/cars-for-sale/results/
/new-cars/results/
/used-cars/results/
Solution 1 - separate actions
If your application is limited to these three categories, the easiest solution is to create three actions, one per category;
Controller:
class ListingsController extends AppController
{
const CATEGORY_NEW = 1;
const CATEGORY_USED = 2;
const CATEGORY_FOR_SALE = 3;
public $uses = array('Car');
public function forSaleCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_FOR_SALE)));
}
public function newCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_NEW)));
}
public function usedCars()
{
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => self::CATEGORY_USED)));
}
}
Routes.php
Router::connect(
'/cars-for-sale/results/*',
array('controller' => 'listings', 'action' => 'forSaleCars')
);
Router::connect(
'/new-cars/results/*',
array('controller' => 'listings', 'action' => 'newCars')
);
Router::connect(
'/used-cars/results/*',
array('controller' => 'listings', 'action' => 'usedCars')
);
Solution 2 - Pass the 'category' as parameter
If the list of URLs to be used for the 'listings' will not be fixed and will expand, it may be better to pass the 'filter' as a parameter and include that in your routes;
routes.php
Router::connect(
'/:category/results/*',
array(
'controller' => 'listings',
'action' => 'results',
),
array(
// category: lowercase alphanumeric and dashes, but NO leading/trailing dash
'category' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
// Mark category as 'persistent' so that the Html/PaginatorHelper
// will automatically use the current category to generate links
'persist' => array('category'),
// pass the category as parameter for the 'results' action
'pass' => array('category'),
)
);
Read about the Router API
In your controller:
class ListingsController extends AppController
{
public $uses = array('Car');
/**
* Shows results for the specified category
*
* #param string $category
*
* #throws NotFoundException
*/
public function results($category = null)
{
$categoryId = $this->Car->Category->field('id', array('name' => $category));
if (!$categoryId) {
throw new NotFoundException(__('Unknown category'));
}
$this->set('cars', $this->Paginator->paginate('Car', array('Car.category_id' => $categoryId)));
}
}
And, to create a link to a certain category;
$this->Html->link('New Cars',
array(
'controller' => 'listings',
'action' => 'results',
'category' => 'new-cars'
)
);
i would like to match all requests where user is unlogged to controller Admin\Controller\Sign and action in. I wrote this code in onBootstrap() method in Module.php file :
if (!$authService->hasIdentity()) {
$routeMatch = new RouteMatch(
array(
'controller' => 'Admin\Controller\Sign',
'action' => 'in'
)
);
$event->setRouteMatch($routeMatch);
}
I don't get any errors, but code doesn't work, why?
The problem here is that the application route event (MvcEvent::EVENT_ROUTE) is triggered after the (MvcEvent::EVENT_BOOTSTRAP).
Which means even if you're setting the route match at the bootstrap level, the application is going to override it with the route match of the request after the MvcEvent::EVENT_ROUTE.
If you want to avoid this overriding you need to add a listener for the route event with a very low priority to make sure it will not be overridden:
$e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRouteEvent'), -1000000);
Note : the onRouteEvent would be the method of your Module class that handles the route event (similar to your code).
If you want to short-circuit your application running at the bootstrap level, what you can do is to send the headers with redirection code to the client:
//get the url of the login page (assuming it has route name 'login')
$url = $e->getRouter()->assemble(array(), array('name' => 'login'));
$response=$e->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
add a route entry sign_in as below in the routes section of the module.config.php under admin module
'sign_in' => array(
'type' => 'Segment',
'options' => array(
'route' => '/admin/sign/in',
'defaults' => array(
'controller' => 'sign',
'action' => 'in',
),
),
),
and call the route in the controller like this
$this->redirect()->toRoute('sign_in');
I have one route that looks like this:
Router::connect('/Album/:slug/:id',array('controller' => 'albums', 'action' => 'photo'),array('pass' => array('slug','id'),'id' => '[0-9]+'));
and another like this:
Router::connect('/Album/:slug/*',array('controller' => 'albums','action' => 'contents'),array('pass' => array('slug')));
for what doesn't match the first. In the 'contents' action of the 'albums' controller, I take care of pagination myself - meaning I retrieve the named parameter 'page'.
A URL for the second route would look like this:
http://somesite.com/Album/foo-bar/page:2
The Above URL indeed works, but when I try to use the HTML Helper (url,link) to output a url like this, it appends the controller and action to the beginning, like this:
http://somesite.com/albums/contents/Album/foo-bar/page:2
Which i don't like.
The code that uses the HtmlHelper is as such:
$html->url(array('/Album/' . $album['Album']['slug'] . '/page:' . $next))
See below url it is very help full to you
http://book.cakephp.org/2.0/en/development/routing.html
Or read it
Passing parameters to action
When connecting routes using Route elements you may want to have routed elements be passed arguments instead. By using the 3rd argument of Router::connect() you can define which route elements should also be made available as passed arguments:
<?php
// SomeController.php
public function view($articleId = null, $slug = null) {
// some code here...
}
// routes.php
Router::connect(
'/blog/:id-:slug', // E.g. /blog/3-CakePHP_Rocks
array('controller' => 'blog', 'action' => 'view'),
array(
// order matters since this will simply map ":id" to $articleId in your action
'pass' => array('id', 'slug'),
'id' => '[0-9]+'
)
);
And now, thanks to the reverse routing capabilities, you can pass in the url array like below and Cake will know how to form the URL as defined in the routes:
// view.ctp
// this will return a link to /blog/3-CakePHP_Rocks
<?php
echo $this->Html->link('CakePHP Rocks', array(
'controller' => 'blog',
'action' => 'view',
'id' => 3,
'slug' => 'CakePHP_Rocks'
));