Routing in CakePHP to vanity urls - php

I was wondering if there was an easy and best practices way to make routes in CakePHP (routes.php file) to map userIDs to a vanity url?
I have (terrible way to do this) the following test code in my routes page:
$users = array
(
1 => 'firstname-lastname',
2 => 'firstname2-lastname2'
);
//profiles
foreach($users as $k => $v)
{
// LESSONS (Profiles)
Router::connect('/:user', array('controller' => 'teachers', 'action' => 'contentProfile', $k),
array('user' => '(?i:'.$v.')'));
}
The above code routes my teachers controller with conProfile as the action from:
mydomain.com/teachers/contentProfile/1
to
mydomain.com/firstname-lastname
Can I connect to the db from the routing page? Is that not a good idea in terms of performance? Let me know what's the best way to do this.

You can create a custom route class that will look up passed urls in the database and translate them to the correct user id. Setting a long cache time should mitigate any performance impact of hitting the DB.
The book documentation is a little thin, however, but the basic structure is this:
class TeachersRoute extends CakeRoute {
/**
* Modify incoming parameters so that controller receives the correct data
*/
function parse($url) {
$params = parent::parse($url);
// Add / modify parameter information
// The teacher id should be sent as the first value in the $params['pass'] array
return $params;
// Or return false if lookup failed
}
/**
* Modify parameters so calls like HtmlHelper::url() output the correct value
*/
function match($url) {
// modify parameters
// add $url['slug'] if only id provided
return parent::match($url);
}
And then in your routes:
Router::connect(
'/:slug',
array(
'controller' => 'teachers',
'action' => 'contentProfile'
),
array(
'slug' => '[a-zA-Z0-9_-]+'
'routeClass' => 'TeachersRoute',
)
);

Related

Pass in hard coded params into named Laravel route

I am creating some hard coded routes that will likely be changed again freely in the future. To abstract the ideas a bit:
We have a controller/method BuySubscriptionController#start:
class BuySubscriptionController
function start()
{
$plan = Plan::findBySlug($request->get('plan'));
return view('someView', ['plan' => $plan]);
}
}
We currently have the following route:
Route::get('/buy-subscription/start', 'BuySubscriptionController#start');
This means the sales team would need to advertise the following urls:
site.com/buy-subscription/start?plan=plan-one
site.com/buy-subscription/start?plan=plan-two
Now we have been requested to have a few specialized routes:
site.com/purchase/the-basic-plan (plan-one)
site.com/purchase/the-mega-plan (plan-two)
Now I am trying to add these specialized urls to my routes. I was hoping to do something as follows, but does not work:
Route::get('/purchase/the-basic-plan', [
'uses' => 'BuySubscriptionController#start',
'with' => ['plan' => 'plan-one']
]);
Route::get('/purchase/the-mega-plan', [
'uses' => 'BuySubscriptionController#start',
'with' => ['plan' => 'plan-two']
]);
Is there any way to achieve this, simply, without over engineering some new translation layer? Keep in mind that next week the url might be /buy/the-god-plan meaning plan-one, so being able to simple add a line to my routes seems ideal.
You can define a route that takes the plan as a parameter, and use Regular Expression Constraints so that parameter can only take certain values that you allow. So your route definition can look like this:
Route::get('/purchase/{plan}', 'BuySubscriptionController#start')
->name('purchase-plan')
->where('plan', 'the-basic-plan|the-mega-plan');
Then in your controller action just use the parameter:
class BuySubscriptionController
{
protected $plans = [
'the-basic-plan' => 'plan-one',
'the-mega-plan' => 'plan-two'
];
function start($plan)
{
// You can use an associative array to convert the $plan parameter
// into the value you need for querying the database
$plan = Plan::findBySlug($this->plans[$plan]);
return view('someView', ['plan' => $plan]);
}
}
If you need to generate URLs for the route you can just use the route helper method and pass it the plan name:
route('purchase-plan', 'the-basic-plan');
And you'll get:
site.com/purchase/the-basic-plan
This solution allows you to add any number of plan names by just adding the public plan for the URL in the where constraint of the route, then associating that value with the one you need for the query in your controller's $plans property.

CakePHP 3 best way to add additional data to url

I wanted to ask, about strategy, how can i archive this:
I have url: www.mydomain.com/pages
Now, if some if clausure will return true, i want attach this param to all urls:
www.mydomain.com/pages?id=swa or
www.mydomain.com?id=swa
I have no idea how to start,
Thank You for help.
Your best bet would probably be to use a URL filter, it will affect all URLs that are being generated using the core helpers or the Router class, as long as they are being passed as routing arrays, ie
['controller' => 'abc', 'action' => 'xyz', /* ... */]
URLs passed as strings, like /abc/xyz will not be affected!
\Cake\Routing\Router::addUrlFilter(function ($params, $request) {
$key = 'id';
$value = 'swa';
if (!array_key_exists($key, $params)) {
$params[$key] = $value;
}
return $params;
});
This would add the parameter to all URLs (unless they already have that parameter set). But be careful, in case there is a matching connected route that defines a route element with the same name, it will steal the parameter and use it for the element instead of adding it to the query string!
Also note that this will only affect form actions that do explicitly define an action URL, if you'd wanted it to affect the ones that pick up the current URL too, then you'd also have to modify $request->query
$request->query[$key] = $value;
See also
Cookbook > Routing > Creating Persistent URL Parameters
API > \Core\Routing\Router::addUrlFilter()
If it is a one off you can do that using redirects
return $this->redirect(['controller' => 'Pages', 'action' => 'display', $pageId]);
or via html helper
echo $this->Html->link(
'Page 1',
['controller' => 'Pages', 'action' => 'display', 1]
);

CakePHP, Routing for optional controller using generic controller otherwise

I'd like to make an application in CakePHP which manages exercises and users results. Users and results are not important in this question.
I want to have a possibility to add an exercise with adding only a specific table and line to .ini config file. Application should route to a GenericExercisesController if specific one doesn't exists. Controller should load a GenericExerciseModel if specific doesn't exists. I'd managed with model loading and partially with routing with controller like this:
In route.php
foreach(Configure::read('exercisesTables') as $exerciseName){
if( App::import('Controller', Inflector::pluralize($exerciseName))){
Router::connect('/exercises/'.Inflector::pluralize($exerciseName).'/:action', array('controller' => Inflector::pluralize($exerciseName)));
}else{
Router::connect('/exercises/'.Inflector::pluralize($exerciseName).'/:action', array('controller' => 'GenericExercises', 'fakeModel' => $exerciseName));
}
}
So if I want to load an exercise Foo I should use address:
http://example.com/exercises/Foos/view
And this works fine, doesn't matter if specific controller exists.
Problem begins when I use reverse routing to generate links in views. If exercise Foo have specific controller this works correctly:
print $this->Html->url(array('controller' => Inflector::pluralize($exerciseName), 'action' => 'view'));
produces:
/exercises/Foos/view
But when exercise Bar doesn't have specific controller then the same code produces:
/Bars
This causes a problem, there is no Bars Controller.
Temporarily I'm generating those links manually, but I don't think that this is the best solution:
print $this->Html->url("/".Configure::read('exerciseRoutingPrefix')."/".Inflector::pluralize($exerciseName)."/view");
Maybe someone of you know a better solution. Thank you for reading my question.
UPDATE 1:
Those are routes in route.php defined before foreach in order as they're in file:
Router::connect('/', array('controller' => 'questions', 'action' => 'regulations'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Router::connect('/help', array('controller' => 'pages', 'action' => 'faq'));
If I understood your question correctly, you could try making a "MyHtmlHelper" which does some magic before parsing your options to the HtmlHelper::link();
You can do it like so:
/app/View/Helpes/MyHtmlHelper.php
Create a helper which extends the "HtmlHelper" like so:
<?php
App::uses('HtmlHelper', 'View/Helper');
class MyHtmlHelper extends HtmlHelper {
public function link($title, $url = null, $options = array(), $confirmMessage = false) {
//do your magic here and change the $title, $url or $options accordingly.
return parent::link($title, $url, $options, $confirmMessage);
}
}
Now in your controller, you should include the Helper like so (aliasing) $helpers = array('Html' => array('className' => 'MyHtml')); instead of $helpers = array('Html');.
I guess you can fill in the blanks on the public function link yourself. If not, feel free to ask more help :)
More about helpers
Off course it is possible to do this with every other Helper or method you can think off. HtmlHelper::link() is just used as example.

Append param to current URL with view helper in Zend

In my layout-script I wish to create a link, appending [?|&]lang=en to the current url, using the url view helper. So, I want this to happen:
http://localhost/user/edit/1 => http://localhost/user/edit/1?lang=en
http://localhost/index?page=2 => http://localhost/index?page=2&lang=en
I have tried the solution suggested Zend Framework: Append query strings to current page url, accessing the router directly, but that does work.
My layout-script contains:
English
If the default route is used, it will append /lang/en, but when another route is used, nothing is appended at all.
So, is there any way to do this with in Zend without me having to parse the url?
Edit
Sorry for my faulty explanation. No, I haven't made a custom router. I have just added other routes. My bad. One route that doesn't work is:
$Routes = array(
'user' => array(
'route' => 'admin/user/:mode/:id',
'defaults' => array('controller' => 'admin', 'action' => 'user', 'mode'=>'', 'id' => 0)
)
);
foreach( $Routes as $k=>$v ) {
$route = new Zend_Controller_Router_Route($v['route'], $v['defaults']);
$router->addRoute($k, $route);
}
Upd:
You must add wildcard to your route or define 'lang' parameter explicitly.
'admin/user/:mode/:id/*'
Additionally, according to your comment, you can do something like this:
class controllerplugin extends Zend_Controller_Plugin_Abstract
{
function routeShutdown($request)
{
if($request->getParam('lang', false) {
//store lang
Zend_Controller_Front::getInstance()->getRouter()->setGlobalParam('lang', NULL); //check if this will remove lang from wildcard parameters, have no working zf installation here to check.
}
}
}

Zend Framework Router dynamic routes

I bumped into a problem and I can't seem to find a good solution to make it work. I have to make some dynamic routes into a Zend Framework project. I'll explain shortly what my problem is:
I need to have dynamic custom routes that "extend" the default route (module/controller/action/params). The project I'm working for has several partners and the routes have to work with those.
To store the partners I've made a static class and it looks like this.
<?php
class App_Partner
{
static public $partners = array(
array(
'name' => 'partner1',
'picture' => 'partner1.jpg'
),
array(
'name' => 'partner2',
'picture' => 'partner2.jpg'
),
array(
'name' => 'partner3',
'picture' => 'partner3.jpg'
)
);
static public function routePartners() {
$partners = array();
foreach(self::$partners as $partner) {
array_push($partners, strtolower($partner['name']));
}
$regex = '(' . implode('|', $partners) . ')';
return $regex;
}
}
So App_Partner::routePartners() return me a string like (partner1|partner2|partner3) which I use to create the right routes. My goal is to have the custom routes for each partner for every route I have set in the Bootstrap. So if I have a route add-product.html set I want it to work for each partner as partner1/add-product.html, partner2/add-product.html and partner3/add-product.html.
Also, partner1/, partner2/, partner3 should route to default/index/index.
In fact, I made this thing to work using routes like the one below.
<?php
$routeProposal = new Zend_Controller_Router_Route_Regex(
App_Partner::routePartners() . '?/?proposals.html',
array(
'module' => 'default',
'controller' => 'proposal',
'action' => 'index',
'page' => 1
),
array( 1 => 'partner'),
"%s/proposals.html"
);
$router->addRoute('proposal', $routeProposal);
The problem
The above route works fine if I use a partner in the request URI, but if I don't, I get double slashes like public//proposals.html because of the reverse route set in the route above to be "%s/proposals.html". I can't seem to find a way to avoid this reverse route because I build my urls using the url view helper and if the reverse route isn't set I get an exception stating this.
I also need the routes to work without a partner set, which will be the default way (add-product.html, proposals.html etc).
From your description, it seems like you're looking for a zend router chain, where your partner is an optional chain.
Here's a similar question, but using a hostname route : Zend Framework: get subdomain parameter from route. I adapted it to solve your problem, just put the following in your Bootstrap.php to initialize the routing :
protected function _initRoute()
{
$this->bootstrap('FrontController');
$router = $this->getResource('FrontController')->getRouter();
// Default route
$router->removeDefaultRoutes();
$defaultRoute = new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index',
)
);
$router->addRoute('default', $defaultRoute);
$partnerRoute = new Zend_Controller_Router_Route(
':partner',
array('partner' => 'none'),
array('partner' => '^(partner1|partner2|partner3)$')
);
$router->addRoute('partner', $partnerRoute->chain($defaultRoute));
}
Change as you see fit. In your controllers you will only get a value for the partner parameter if it was actually specified AND valid (you will get a routing error if the partner doesn't exist)...
I use a similar process to detech lang, in my route (but with a ini file).
You can use a default value for you partners parameter to make the route working without partner, and add a ? to your regex.
But actually, I don't know how to avoid the double //...
Hope that helps.
EDIT: For your information, here is a simplified version of my route with language:
routes.lang.type = "Zend_Controller_Router_Route"
routes.lang.route = "lang/:language/*"
routes.lang.reqs.language = "^(en|fr|nl|de)?$"
routes.lang.defaults.language = none
routes.lang.defaults.module = default
routes.lang.defaults.controller = index
routes.lang.defaults.action = language

Categories