Routing, Navigation and State in MVC - php

I am attempting to refactor my app using the MVC paradigm.
My site displays charts. The URLs are of the form
app.com/category1/chart1
app.com/category1/chart2
app.com/category2/chart1
app.com/category2/chart2
I am using Apache Rewrite to route all requests to index.php, and so am doing my URL parsing in PHP.
I am working on the enduring task of adding an active class to my navigation links when a certain page is selected. Specifically, I have both category-level navigation, and chart-level sub-navigation. My question is, what is the best way to do this while staying in the spirit of MVC?
Before my refactoring, since the nav was getting relatively complicated, I decided to put it into an array:
$nav = array(
'25th_monitoring' => array(
'title' => '25th Monitoring',
'charts' => array(
'month_over_month' => array(
'default' => 'month_over_month?who=total&deal=loan&prev='.date('MY', strtotime('-1 month')).'&cur='.date('MY'),
'title' => 'Month over Month'),
'cdu_tracker' => array(
'default' => 'cdu_tracker',
'title' => 'CDU Tracker')
)
),
'internet_connectivity' => array(
'title' => 'Internet Connectivity',
'default' => 'calc_end_to_end',
'charts' => array(
'calc_end_to_end' => array(
'default' => 'calc_end_to_end',
'title' => 'calc End to End'),
'quickcontent_requests' => array(
'default' => 'quickcontent_requests',
'title' => 'Quickcontent Requests')
)
)
);
Again, I need to know both the current category and current chart being accessed. My main nav was
<nav>
<ul>
<?php foreach ($nav as $category => $category_details): ?>
<li class='<?php echo ($current_category == $category) ? null : 'active'; ?>'>
<?php echo $category_details['title']; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
and the sub-nav was something similar, checking for current_chart instead of current_category.
Before, during parsing, I was exploding $_SERVER['REQUEST_URI'] by /, and breaking the pieces up into $current_category and $current_chart. I was doing this in index.php. Now, I feel this is not in the spirit of the font controller. From references like Symfony 2's docs, it seems like each route should have its own controller. But then, I find myself having to define the current category & chart multiple times, either within the template files themselves (which doesn't seem to be in the spirit of MVC), or in an arbitrary function in the model (which would then have to be called by multiple controllers, which is seemingly redundant).
What is the best practice here?
Update: Here's what my front controller looks like:
// index.php
<?php
// Load libraries
require_once 'model.php';
require_once 'controllers.php';
// Route the request
$uri = str_replace('?'.$_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']);
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && (!empty($_GET)) && $_GET['action'] == 'get_data') {
$function = $_GET['chart'] . "_data";
$dataJSON = call_user_func($function);
header('Content-type: application/json');
echo $dataJSON;
} elseif ( $uri == '/' ) {
index_action();
} elseif ( $uri == '/25th_monitoring/month_over_month' ) {
month_over_month_action();
} elseif ( $uri == '/25th_monitoring/cdu_tracker' ) {
cdu_tracker_action();
} elseif ( $uri == '/internet_connectivity/intexcalc_end_to_end' ) {
intexcalc_end_to_end_action();
} elseif ( $uri == '/internet_connectivity/quickcontent_requests' ) {
quickcontent_requests_action();
} else {
header('Status: 404 Not Found');
echo '<html><body><h1>Page Not Found</h1></body></html>';
}
?>
It seems like when month_over_month_action() is called, for instance, since the controller knows the current_chart is month_over_month, it should just pass that along. This is where I'm getting tripped up.

There are not "best practices" in this area. Though, there are some, that are more often used then others, and some, that are extremely bad ideas (unfortunately, these two groups tend to overlap).
Routing in MVC
While technically not a part of MVC design pattern, when applied to Web, your application needs to know which controller to initialize and what method(s) to call on it.
Doing explode() to gather this sort of information is a bad idea. It is both hard to debug and maintain. A much better solution is to use regular expressions.
Basically you end up having a list of routes, that contain a regular expression and some fallback values. You loop through that list and on fists match extract the data and apply default values, where data was missing.
This approach also frees you to have much wider possibilities for order of parameters.
To make the solution easier to use, you can also add functionality, that turns a notation string into a regular expression.
For example (taken from some unit-test, that I have):
notation:     test[/:id]
expression: #^/test(:?/(?P<id>[^/\.,;?\n]+))?$#
notation:     [[/:minor]/:major]
expression: #^(:?(:?/(?P<minor>[^/\.,;?\n]+))?/(?P<major>[^/\.,;?\n]+))?$#
notation:     user/:id/:nickname
expression: #^/user/(?P<id>[^/\.,;?\n]+)/(?P<nickname>[^/\.,;?\n]+)$#
While creating such a generator will not be all that easy, it would be quite reusable. IMHO the time invested in making it would be well spent. Also, the use of (?P<key>expression) construct in regular expressions provides you with a very useful array of key-value pairs from the matched route.
Menus and MVC
The decision about which menu item to highlight as active should always be the responsibility of current view instance.
More complicated issue is where the information, that is necessary for making such decision, comes from. There are two source if data that are available to a view instance: information that was passed to view by controller and data, that view requested from model layer.
The controller in MVC takes the user's input and, based on this input, it changes the state of current view and model layer, by passing said values. Controller should not be extracting information from model layer.
IMHO, the better approach in this case is to relay on model layer for information about both menu content and the currently active element in it. While it's possible to both hardcode the currently active element in view and relay on controllers passed informations, MVC is usually used in large scale application, where such practices would end up hurting you.
The view in MVC design pattern is not a dumb template. It's a structure, that is responsible for UI logic. In context of Web that would mean creating a response from multiple template, when necessary, or sometimes just simply sending an HTTP location header.

Well, I had almost the same trouble when was writing CMS-like product.
So I've spend some time trying to figure out how to make this work and keep the code more maintainable and clean as well.
Both CakePHP and Symfony route-mecanisms have a bit inspired me but it wasn't good enough for me.
So I'll try to give you an example of how I do this now.
My question is, what is the best way to do this while staying in the
spirit of MVC?
First, In general, best practice is NOT TO USE procedural approach with MVC in web development at all.
Second, keep the SRP.
From references like Symfony 2's docs, it seems like each route should
have its own controller.
Yeah, that's right approach, but it doesn't mean that another route match can't have the same controller, but different action.
The main disadvantage of your approach (code that you have posted) is that you mix responsibilities and you're not implementing MVC-inspired pattern.
Anyway, MVC in PHP with procedural approach is just a horrible thing.
So, what exactly you are mixing is:
Route mechanism logic (It should be another class) not in a "controller" and route map as well
Request and Response responsibilites (I see that it isn't obvious to you)
Class autoloading
Controller logic
All those "parts" should have one class. Basically, they have to be included in index or bootstrap files.
Also, by doing so:
require_once 'controllers.php';
You automatically include ALL controllers per match (even on no-match). It actually has nothing to do with MVC and leads to memory leaks.
Instead, you should ONLY include and instantiate the controller that matches against URI string.
Also, be careful with include() and require() as they may lead to code duplication if you include the same file somewhere twice.
And also,
} elseif ( $uri == '/' ) {
index_action();
} elseif ( $uri == '/25th_monitoring/month_over_month' ) {
month_over_month_action();
} elseif ( $uri == '/25th_monitoring/cdu_tracker' ) {
cdu_tracker_action();
} elseif ( $uri == '/internet_connectivity/intexcalc_end_to_end' ) {
intexcalc_end_to_end_action();
It's extremely unwise to do a match using if/else/elseif control structures.
Okay, what if you have 50 matches? or even 100? Then you need to write 50 or 100 times to write else/elseif accordingly.
Instead, you should have a map and (an array for example) iterate over it on each HTTP request.
The general approach of using MVC with routing mechanism comes down to:
Matching the request against route map (and keep somewhere parameters if we have them)
Then instantiate appropriate controller
Then pass parameters if we have them
In PHP, the implementation would look like:
File: index.php
<?php
//.....
// -> Load classes here via SPL autoloader or smth like this
// .......
// Then -> define or (better include route map from config dir)
$routes = array(
// -> This should default one
'/' => array('controller' => 'Path_To_home_Controller', 'action' => 'indexAction'),
'/user/:id' => array('controller' => 'Path_to_user_controller', 'action' => 'ViewAction'),
// -> Define the same controller
'/user/:id/edit' => array('controller' => 'Path_to_user_controller', 'action' => 'editAction'),
// -> This match we are going to hanlde in example below:
'/article/:id/:user' => array('controller' => 'SomeArticleController', 'action' => )
);
// -> Also, note you can differently handle this: array('controller' => 'SomeArticleController', 'action' => )
// -> Generally controller key should point to the path of a matched controller, and action should be a method of the controller instance
// -> But if you're still on your own, you can define it the way you want.
// -> Then instantiate common classes
$request = new Request();
$response = new Response();
$router = new Router();
$router->setMap( $routes );
// -> getURI() should return $_SERVER['REQUEST_URI']
$router->setURI( $request->getURI() );
if ( $router->match() !== FALSE ) {
// -> So, let's assume that URI was: '/article/1/foo'
$info = $router->getAll();
print_r ( $info );
/**
* Array( 'parameters' => Array(':id' => '1', ':user' => 'foo'))
* 'controller' => 'Path_To_Controller.php'
* 'action' => 'indexAction'
*/
// -> The next things we are going to do are:
// -> 1. Instantiate the controller
// -> 2. Pass those parameters we got to the indexAction method
$controller = $info['controller'];
// -> Assume that the name of the controller is User_Controller
require ( $controller );
// -> The name of class should also be dynamic, not like this, thats just an example
$controller = new User_Controller();
$arguments = array_values( $info['parameters'] );
call_user_func_array( array($controller, $info['action']), $arguments );
// -> i.e we just called $controller->indexAction('1', 'foo') "dynamically" according to the matched URI string
// -> idealy this should be done like: $response->send( $content ), however
} else {
// -> In order not to show any error
// -> redirect back to "default" controller
$request->redirect('/');
}
In my MVC-inspired applications I do route like this:
(Where I use Dependecy Injection and keep the SRP)
<?php
require (__DIR__ . '/core/System/Auload/Autoloader.php');
Autoloader::boot(); // one method includes all required classes
$map = require(__DIR__ . '/core/System/Route/map.php');
$request = new Request();
$response = new Response();
$mvc = new MVC();
$mvc->setMap( array_values($map) );
// -> array_values($map) isn't accurate here, it'd be a map of controllers
// -> take this as a quick example
$router = new Router();
$router->setMap( $map );
$router->setURI( $request()->getURI() );
if ( $router->match() !== FALSE ) {
// -> Internally, it would automatically find both model and view instances
// -> then do instantiate and invoke appropriate action
$router->run( $mvc );
} else {
// No matches handle here
$request->redirect('/');
}
I found this to be more appropriate for me, after poking around Cake and Symfony.
One thing I want to note:
It's not that easy to find good articles about MVC in PHP. Most of them are just wrong.
(I know how it feels, because first time I've started to learn from them, like so many people do)
So my point here is:
Don't make the same mistake like I did before. If you want to learn MVC, start doing this by reading
Zend Framework or Symfony Tutorials. Even the ones are bit different, the idea behing the scene is the same.
Back to the another part of the question
Again, I need to know both the current category and current chart
being accessed. My main nav was
<nav>
<ul>
<?php foreach($nav as $category => $category_details): ?>
<li class='<?php echo ($current_category == $category) ? null : 'active'; ?>'>
<?php echo $category_details['title']; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
First of all, don't concatenate the string, instead use printf() like:
<?php echo $category_details['title']; ?>
If you need this to be everywhere (or at least in many different templates), I'd suggest to this to have in a common abstact View class.
For example,
abstract class View
{
// -> bunch of view reusable methods here...
// -> Including this one
final protected function getCategories()
{
return array(
//....
);
}
}
class Customers_View extends View
{
public function render()
{
$categories =& $this->getCategories();
// -> include HTML template and then interate over $categories
}
}

Related

How to manage ACL rules?

I'm writing an ACL class that takes its rules from "setter" methods:
<?php
$acl = new AccessControlList();
$acl->allow('someController', 'publicAction', 'guestRole');
$acl->deny('someController', 'privateAction', 'guestRole');
The question is: what's the best option for storing these rules in the ACL object?
At the moment, I'm considering an array like this:
array(
'guest' => array(
'someController' => array(
'publicAction' => true,
'privateAction' => false
)
),
'admin' => array (
...
)
)
But it looks like it will be a performance disaster when it grows, keeping in mind the logic to read the array (infering isAllowed(...) results) and writing it (with rule conflicts, overwrites, inheritance between roles and resources...).
Maybe I'm wrong from the begining and those "setters" are the problem. Is there any well established design pattern to follow?
You can avoid populating the entirety of the ACL list by lazy loading.
This is a very simplistic approach but the concept should scale. This assumes that for a given request most if not all of the acl checks will be against single users role, but the same technique could be used for a controller or a combination of role and controller.
Define the acl in a tabular way (csv, json, ini, db table, or php include) whatever just so you can get, an array that contains allow or deny, action, and role. I'm going to go with a php include because it assumes the least. You would have one of these for each of the roles.
return array(
array("allow", "someController", "someAction"),
array("deny", "someController", "someOtherAction")
);
Add a method onto your AccessControlList to read and process such a file.
protected function readConfig($role, $source){
$dat = include($source);
foreach($dat as $rule){
switch($rule[0]){
case "allow": $this->allow($rule[2], $rule[3], $role); break;
case "deny": $this->deny($rule[2], $rule[3], $role); break;
}
}
}
Add a method to bind an role to a file. this will simply add to your existing acl
public function setRules($role, $source){
$this->acl[$role] = $source;
}
Then in your isAllowed check to see if the role's acl node is a string or an array
public function isAllowed($role, $subject, $action){
if (is_string($this->acl[$role])){
$this->readConfig($role, $this->acl[$role]);
}
// do your acl check as normal.
}

CakePHP: Redirect in routes.php

OK, I dont know if I am taking the wrong approach or not but am stuck here...
We have developed our website and we have many controllers expecting ids and special variables, links already redirecting to the controllers passing what is expected.
The new requirement is to use friendlyUrls and the idea is that instead of having:
http://domain.com/search/advanced/term:head/city:/set:show-all/sort:basic-relevance
it now reads
http://domain.com/search/head
or passing options.
http://domain.com/search/in-edinburgh-scotland/by-rating/head
My idea was to, at the beginning of the Routes.php have a simple if such as:
$friendlyUrl = $_SERVER['REQUEST_URI'];
$friendlyUrl = split('/', $friendlyUrl);
foreach ($friendlyUrl as $key => $params) {
if(empty($params)){
unset($friendlyUrl[$key]);
}
if($params == 'search'){
Router::connect('/search/*', array('plugin'=>'Search','controller' => 'Search', 'action' => 'advancedSearch', 'term'=>'head));
}elseif ($params == 'employers') {
# code...
}elseif ($params == 'employer-reviews') {
# code...
}elseif ($params == 'jobs') {
# code...
}
}
That didn't work, then I tried adding something similar in my AppController and nothing.
All in all the the thing that has to do is:
Url be in the format of: /search/{term}
Actually be redirecting to: /search/advanced/{term}/city:{optional}/set:show-all/sort:basic-relevance
URL bar to keep reading: /search/{term}
Anyone has an idea?! Thank you
You definitely want to have a look at the routing page in the book
http://book.cakephp.org/2.0/en/development/routing.html
There are tons of options there to match url patterns to pass parameters to the controllers.
Router::connect(
'/search/:term',
array('controller' => 'search', 'action' => 'advanced'),
array(
'pass' => array( 'term')
)
);
You should probably set the defaults for city & set & sort in the actions function parameters definitions:
public function advanced($term, $city='optional', $sort = 'basic'){
// your codes
}
The great thing about doing it this way, is that your $this->Html->link's will reflect the routes in the paths they generate. (reverse routing)
The routes in cake are quite powerful, you should be able to get some decent friendly urls with them. One extra thing I've used is to use a behaviour - sluggable - to generate a searchable field from the content items title - for pages / content types in the cms.
good luck!

kohana: Remove need for default controller route

I have a route as:
((?<directory>\w+)/?)?((?<controller>\w+)/?)?((?<action>\w+)/?)?((?<id>\d+))?
It works fine but it causes my system to have to include the default controller (index) for all routes to the sub routes. For example, if my page URI is /blog/post (where blog is the directory and post would be the action), my actual URI would have to be blog/index/post - I'd like to be able to fall back to just using blog/post instead.
So, I would like it to be routed to:
directory = blog
controller = index
action = post
Obviously this causes issues when the second parameter is actually a controller. For example directory/controller/action would be routed incorrectly.
Is there a routing method to detect that there are three word parameters, possibly followed by a numeric parameter, which can do what I need?
For claification:
param/param/param(?/id) would be: directory/controller/action(/id)
param/param(?/id) would be: directory/default_controller/action(/id)
i'd actually think that you want to alias blog/index/post with blog/post; insert it as a route before the "catch-all" route that you have; the "one big shoe fits all" approach is not always the best. Especially, if you only have 1 such particular use case.
edit:
"kohana's routing system" is daunting; can't make sense of the elephant they're trying to give birth to there... here are some other suggestions:
Take this issue to the manufacturer; this is definetely an FAQ question
Mess around with the regex patterns. Here's a snippet that might be useful (i put it inside a PHP test case, but you could easily decouple it)
public function testRoutePatterns(){
$data = array(
array(
//most specific: word/word/word/id
'~^(?P<directory>\w+)/(?P<controller>\w+)/(?P<action>\w+)/(?P<id>.*)$~i',
'myModule/blog/post/some-id',
array('directory'=>'myModule', 'controller'=>'blog', 'action'=>'post', 'id'=>'some-id'),
true
),
array(
//less specific: word/word/id
'~^(?P<directory>\w+)/(?P<action>\w+)/(?P<id>.*)$~i',
'blog/post/some-id',
array('directory'=>'blog', 'action'=>'post'), //need to inject "index" controller via "defaults()" here i guess
true
),
);
foreach ($data as $d) {
$matches = array();
list($pattern, $subject, $expected, $bool) = $d;
$actual = (bool) preg_match($pattern, $subject, $matches);
$this->assertEquals($bool, $actual); //assert matching
$this->assertEquals(array(), array_diff($expected, $matches)); //$expected contained in $matches
}
}
As explained on this answer, if you have some route like this:
Route::set('route_name', 'directory/controller/action')
->defaults(array(
'directory' => 'biz',
'controller' => 'foo',
'action' => 'bar',
));
You should have the directory structure like this:
/application/classes/controller/biz/foo.php

Best practice to place meta tags, links and styles in zend framework?

I have project-range meta tags that are need to be set.
I've put them in protected method _initMeta in Bootstrap class.
Are there any better options? What if I would like different set of this data for another languages?
protected function _initMeta(){
$this->bootstrap('view');
$view = $this->getResource('view');
$view->doctype('XHTML1_STRICT');
$view->headTitle()->headTitle('Foo title');
$view->headMeta()->appendName('keywords','foo');
$view->headMeta()->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8')
->appendHttpEquiv('Content-Language', 'any');
$view->headLink()->appendStylesheet('/foo.css')->headLink(array('rel' => 'favicon',
'href' => '/favicon.ico'),
'PREPEND');
}
I use config for basic (bootstrap) data as:
application.ini
resources.view.meta.name.Viewport = "width=device-width, initial-scale=1.0"
resources.view.meta.name.MobileOptimized = "width"
resources.view.meta.name.HandheldFriendly = "true"
resources.view.meta.name.Keywords = "basic,keywords"
...
; format resources.view.headStyle.{MEDIA}.nfile =
resources.view.headStyle.all.1.href = "/css/basic.css"
resources.view.headStyle.all.1.conditionalStylesheet =
resources.view.headStyle.all.1.extras.title = "Basic style"
resources.view.headStyle.all.1.extras.charset = "utf-8"
resources.view.headStyle.all.2.href = "/css/ie.css"
resources.view.headStyle.all.2.conditionalStylesheet = "IE"
resources.view.headStyle.all.2.extras.title = "Internet Explorer style"
resources.view.headStyle.all.2.extras.charset = "utf-8"
; print media example
resources.view.headStyle.print.1.href = "/css/print.css"
...
; format resources.view.headLink.{REL} =
resources.view.headLink.humans.href = "/humans.txt"
resources.view.headLink.humans.type = "text/plain"
; ___ will be replaced by space, __ by point (or set another nest separator)
resources.view.headLink.shortcut___icon.href = "/favicon.png"
resources.view.headLink.shortcut___icon.type = "image/png"
...
At this point, maybe you have some special data. For example in:
project1.ini
project.headLink.author.href = "https://plus.google.com/XXXXX?rel=author"
project.headLink.image_src.href = "/author.jpg"
project.headLink.image_src.type = "image/jpg"
And finally, you mix all in your
Bootstrap.php
(example for *_initHeadLink()*):
// $options = your app options (basic)
// $projectOptions = your project options (special)
// $assets_url = your assets url
if ( is_array($headStyle = $options['headStyle']) ) {
foreach ( $headStyle as $media => $value ) {
foreach ( $value as $style ) {
extract($style);
$this->view->headLink()->appendStylesheet($assets_url . $href, $media,
$conditionalStylesheet, $extras);
}
}
}
$headLinks = array();
if ( isset($options['headLink']) )
$headLinks = $options['headLink'];
if ( isset($projectOptions['headLink']) )
$headLinks = array_merge($headLinks, (array) $projectOptions['headLink']);
// *array key, is the value for rel
foreach ( $headLinks as $rel => $value ) {
$rel = str_replace(array('___', '__'), array(' ', '.'), $rel);
$this->view->headLink()->headLink(array_merge(array('rel' => $rel), (array) $value));
}
Then, you can override these data from your Controller: setName, set...
I hope it helps ;)
You have several ways of achieving this. First and foremost, make sure the moment you define the metadata you already know what language will be loaded for the current request. Sometimes this may not be easy to determine at bootstrap time.
Having said this, besides the bootstrap, you can set the metadata on a:
Front Controller Plugin class (using for example the dispatchLoopStartup() or predispatch() methods).
Action Helper class (using for example the init() or preDispatch() methods).
At those points of execution you probably already determined the language to use, and can set the metadata accordingly. You can always change the metadata afterwards in your action controllers for specific cases in your application, so you're never really stuck if you previously specified metadata.
In my own work, I have this setup:
Front Controller Plugin, dispatchLoopStartup() method: determine language to load, giving priority to a "lang" GET parameter in the request object, then browser language, then default site language. I also use this to determine if the request is a normal request or an ajax request; if it's the former case, I register an action helper, see next...
Action Helper, preDispatch() method: load metadata depending on language and other stuff, load Layout and widgets, etc.
Action controller, someAction() method: if necessary, change some of the previously set metadata, for example headTitle(), which can depend on the effectively loaded content.
This arrangement makes sense to me, maybe it can help your approach?
The bootstrap is way to early for my projects. I add them in my controller/actions
$keywords = 'php,zend,framework';
$this->view->headMeta($keywords,'keywords','name',array(),'SET');
... etc.
Actually very late almost at the end. At this point I would also know about language and other things.

Routing a subdomain in CakePHP with HTML-Helper

I have a mobile page running on a subdomain "m.mydomain.com". This is all working fine, but I would like to remove the controller in the URL when using the subdomain.
m.mydomain.com/mobiles/tips
should become
m.mydomain.com/tips
by using the HTML-Helper.
At the moment a link looks like that:
$html->link('MyLink', array('controller' => 'mobiles', 'action'=> 'tips'));
I tried several possible solutions with the routes and also some hacks in the bootstrap but it did not work out for me.
In the CakeBakery I found this but that does not solve my issue.
Does anyone have an idea for this issue?
Gathering code from the page you mentioned:
Constraint: you cannot have a controller called tips or foo in this setup
In /config/routes.php:
$subdomain = substr( env("HTTP_HOST"), 0, strpos(env("HTTP_HOST"), ".") );
if( strlen($subdomain)>0 && $subdomain != "m" ) {
Router::connect('/tips',array('controller'=>'mobiles','action'=>'tips'));
Router::connect('/foo', array('controller'=>'mobiles','action'=>'foo'));
Configure::write('Site.type', 'mobile');
}
/* The following is available via default routes '/{:controller}/{:action}'*/
// Router::connect('/mobiles/tips',
// array('controller' => 'mobiles', 'action'=>'tips'));
// Router::connect('/mobiles/foo',
// array('controller' => 'mobiles', 'action'=>'foo'));
In your Controller action:
$site_is_mobile = Configure::read('Site.type') ?: '';
Then in your view:
<?php
if ( $site_is_mobile ) {
// $html will take care of the 'm.example.com' part
$html->link('Cool Tips', '/tips');
$html->link('Hot Foo', '/foo');
} else {
// $html will just output 'www.example.com' in this case
$html->link('Cool Tips', '/mobiles/tips');
$html->link('Hot Foo', '/mobiles/foo');
}
?>
This will allow you to output the right links in your views (in a bit I'll show you how to write even less code) but the $html helper will not be able -- by no amount of magic -- to use controller-action routes to another domain. Be aware that m.example.com and www.example.com are different domains as far as the $html helper is concerned.
Now, if you want you can do the following in your controller to take some logic off your view:
<?php
$site_is_mobile = Configure::read('Site.type') ?: '';
if ( $site_is_mobile !== '' ) {
$tips_url = '/tips';
$foo_url = '/foo';
} else {
$tips_url = '/mobile/tips';
$foo_url = '/mobile/foo';
}
// make "urls" available to the View
$this->set($tips_url);
$this->set($foo_url);
?>
And in your view you don't need to worry about checking whether the site is being accessed via m.example.com/tips or www.example.com/mobile/tips:
<?php echo $html->link("Get some kewl tips", $tips_url); ?>
For more advanced routing in CakePHP-1.3 refer to Mark Story's article on custom Route classes
Let us know ;)

Categories