Situation: only main page is accessible by default, all other pages needs a logged in user. When a module is loaded without user, a login template should be displayed, and no module. In other words, the $sf_content must be emptied in layout.php which is not 100% ok since there is logic in the layout. Is there elegant way for that? I dont think a helper is OK either....
Check out security filters, this is one standard way security is designed in symfony.
You even can implement your own SecurityFilter class with the functionality you want.
http://symfony.com/legacy/doc/reference/1_4/en/12-Filters#chapter_12_security
It is done by default for you by the sfBasicSecurityFilter filter. You just need a good configuration. Read this part of the Jobeet tutorial. You should use sfDoctrineGuardPlugin (or sfGuardPlugin if you using propell) for user authentication.
To complete my comments above: There are different ways to override the layout. You could use the methods:
setLayout($name)
//or using foward, which forwards current action to a new one (without browser redirection)
forward($module, $action);
inside your action class. In case you wand to modify the layout inside a filter, you can use something simular to this:
class yourFilter extends sfFilter {
public function execute($filterChain) {
if($yourConditionForOverrideTheDefaultLayout) {
//here the syntax to change the layout from the filer
$actionStack = $this->getContext()->getActionStack();
$actionStack->getFirstEntry()->getActionInstance()->setLayout('yourLayout');
}
$filterChain->execute();
}
}
To avoid unnecessary duplication in the layout file you can work with Fragments and Partials.
I create a URL like this:
$app->createAbsoluteUrl('/', array(
'param1' => 'val1',
'param2' => 'var2',
);
The generated URL is:
http://mysite.com/param1/var1/param2/var2
But I expect a url like this:
http://mysite.com/?param1=var1¶m2=var2
In function manual it says:
$params array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
But it doesn't seem to work like that. How I can generate the expected URL? Thanks.
You need to specify that the urlManager application component should use the "get" format for the URLs it generates; the default is to use the "path" format. The Yii guide explains how to do it inside your application configuration:
array(
......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'get',
),
),
);
Update: So your urlFormat is "path" and that's by design... what about alternatives?
If you don't mind extending CWebApplication and using your own derived class in its place then you have several options such as:
Define your own createUrlEx method based on the original createUrl. It could look like this:
public function createUrlEx($format,$route,$params=array(),$ampersand='&')
{
$oldFormat = $this->getUrlManager()->getUrlFormat();
$this->getUrlManager()->setUrlFormat($format);
$url = $this->getUrlManager()->createUrl($route,$params,$ampersand);
$this->getUrlManager()->setUrlFormat($oldFormat);
return $url;
}
Override registerCoreComponents so that you can have a second url manager:
protected function registerCoreComponents()
{
parent::registerCoreComponents();
$components=array(
'specialUrlManager'=>array(
'class'=>'CUrlManager',
'urlFormat'=>'get',
),
);
$this->setComponents($components);
}
You can now call Yii::app()->specialUrlManager->createUrl(...) anytime.
You can also approach the problem in other ways:
Extend CUrlManager and expose a method that allows you to select the flavor of url to create on the spot.
If you only need "get" urls in one or two places, you can always create a new CUrlManager object, configure it on the spot, call createUrl and then discard it. You could also hide this ugliness behind a free function. Essentially this (admittedly not recommended) approach is a low-tech version of the first workaround given that has the advantage that you don't need to extend CWebApplication.
You should be able to use something like Yii::app()->urlManager->createPathInfo This will generate the query string as ...&var=val&... using a custom & and = if you like. You could use this to create a query string version of a URL on demand with:
$url = $this->createAbsoluteUrl('/').'index.php?'.Yii::app()->urlManager->createPathInfo($arrayOfStuff);
Or you might even be able to do:
Yii::app()->urlManager->urlFormat = 'get';
$this->createAbsoluteUrl('My/Path');
Yii::app()->urlManager->urlFormat = 'path';
Although I haven't and don't want to test the second method.
URL route should be in the format of 'ControllerID/ActionID'. manual
Your manual link is for CController and isn't for CApplication.
I've volunteered to create some db app, and I told those guys that it will be very easy, since I wanted to use CakePHP. Sadly after some time they told me they want it inside their already existing web, which is ancient highly customized PHPNuke.
So what I want is to generate just content of one <div> inside an already existing page with CakePHP. I looked up on the internet, but I didn't find what I was looking for. I'm rather a user of the framework, not developer, so I don't know much about the backend and how MVC frameworks are working inside (and this is my first try with CakePHP, since I'm Rails guy).
What I did so far is disabling mod_rewrite for Cake. Inside PHPNuke module I included Cake's index.php and rendering views with an empty layout. This somehow works, but the thing is how to form URLs. I got it working by now with
http://localhost/modules.php/posts?op=modload&name=xxxxx&file=index&do=xxxxx
but with this all links to CSS and images on PHPNuke site are broken.
Is there any way to use something like
http://localhost/modules.php?op=modload&name=xxxxx&file=index&do=xxxxx&CakePHP=/posts/bla/bla
or any other way that could do the job? I really don't want to change anything in existing PHPNuke app.
Thank you very much
Well, if you don't understand how CakePHP works you'll have trouble doing what you want, since it would mean putting hacks into the CakePHP core files to bypass the default routing. This basically means that you would be re-working the way CakePHP works, so you can forget about ever updating to a newer CakePHP version, and maintenance would be hell.
If you want to modify the system, but keep PHP-Nuke, I'd advise against jamming CakePHP in there, since that would open up too many problems to be able to predict beforehand.
I think your options are as follows:
Learn how PHP-Nuke works so you can modify it
Use regular php for the pages
Either of those are easier by orders of magnitude compared to what you wanted to do.
So to sum up solution I found, if someone will be looking for something similar. Problem solved by using two custom route classes ( http://manual.cakephp.neoboots.com/2.0/en/development/routing.html#custom-route-classes )
class CustomParserRoute extends CakeRoute {
function parse($url) {
if (parent::parse($url) != false) //if default parser has the match continue
{
// call to Router class to do the routing for new url string again,
// if &cakePHP= is in query string, use this, or use default
if ($_GET['cakePHP']) {
$params = Router::parse($_GET['cakePHP']);
} else {
$params = Router::parse("/my_controller");
}
return $params;
}
return false;
}
}
class CustomMatcherRoute extends CakeRoute {
// cusotm mathc function, that generates url string.
// If this route matches the url array, url string is generated
// with usual way and in the end added to url query used by PHPNuke
function match($url) {
$result_url = parent::match($url);
if($result_url!= false) {
$newurl = function_to_generate_custom_query()."&cakePHP=".$result_url;
return $newurl;
} else {
return $result_url;
}
}
}
And then simple configuration in routes php
App::import('Lib', 'CustomParserRoute');
App::import('Lib', 'CustomMatcherRoute');
// entry point to custom routing, if route starts with modules.php it matches
// the url and CustomParserRoute::parse class is called
// and route from query string is processed
Router::connect('/modules.php', array('controller' => 'my_controller'), array('routeClass' => 'CustomParserRoute'));
// actual routes used by cakephp app, usual routes that need to use
// CustomMatcherRoute classe, so when new url is generated, it is modified
// to be handled later by route defined above.
Router::connect('/my_controller/:action/*', array('controller' => 'my_controller'), array('routeClass' => 'CustomMatcherRoute'));
I am new to MVC so this is my first attempt and I am sure you guys can give me improvement on this, thanks for any tips or help!
Below is what I have come up with for a router/dispatcher system for my personal framework I am working on, it is my first attempt at using the MVC pattern.
The first block of code is just my .htaccess file which routes all request through my index.php file.
The second block of code is my array of "Routes" which will tell the Router object, which class and method to call as well as any ID or paging numbers if they exists.
Third block of code is the router class.
Fourth block is just running the class
So the router class has to use regex to match the URI with a route in the route map, in theory, this just sounds like bad performance when there is a list of 50+ routes that the regex has to run on, should I be doing this differently? The main reason I use the regex is to match page numbers and ID numbers when they exists in the route.
Also please do not just tell me to use a framework, I am doing this to learn it better, I learn better this way and just prefer to not use an existing framework at this time, I have studies all the main ones and some less common ones for ideas already.
1) So the main question, does anything just not look right?
2) Is there a better way to detect what is in the URI than using the regex on an array like I am doing, consider it on a high traffic site?
3) Since everything is routed through the index.php file with this, how would I go about handling AJAX requests?
Sorry if this is confusing, I am a little confused mtyself!
.htaccess file
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?uri=$1 [NC,L,QSA]
Map array()
/**
* Map URI to class/method and ID and Page numbers
* Must be an array
*/
$uri_route_map = array(
//forums
'forums/' => array(
'controller' => 'forums',
'method' => 'index',
'id_number' => '',
'page_number' => ''),
'forums/viewforum/(?<id_number>\d+)' => array(
'controller' => 'forums',
'method' => 'viewforum',
'id_number' => isset($id_number),
'page_number' => ''),
'forums/viewthread/(?<id_number>\d+)' => array(
'controller' => 'forums',
'method' => 'viewthread',
'id_number' => isset($id_number),
'page_number' => ''),
'forums/viewthread/(?<id_number>\d+)/page-(?<page_number>\d+)' => array(
'controller' => 'forums',
'method' => 'viewthread',
'id_number' => isset($id_number),
'page_number' => isset($page_number)),
// user routes
// account routes
// blog routes
// mail routes
// various other routes
);
Router class that reads and matches the Map array above
/**
* Run URI against our Map array to get class/method/id-page numbers
*/
class Router
{
private $_controller = '';
private $_method = '';
public $page_number = '';
public $id_number = '';
public function __construct($uri, array $uri_route_map)
{
foreach ($uri_route_map as $rUri => $rRoute)
{
if (preg_match("#^{$rUri}$#Ui", $uri, $uri_digits))
{
//if page number and ID number in uri then set it locally
$this->page_number = (isset($uri_digits['page_number']) ? $uri_digits['page_number'] : null);
$this->id_number = (isset($uri_digits['id_number']) ? $uri_digits['id_number'] : null);
$this->_controller = $rRoute['controller'];
$this->_method = $rRoute['method'];
// just for debug and testing while working on it / will be removed from final code
echo '<hr> $page_number = ' . $this->page_number . '<br><br>';
echo '<hr> $id_number = ' . $this->id_number . '<br><br>';
echo '<hr> $controller = ' . $this->_controller . '<br><br>';
echo '<hr> $method = ' . $this->_method . '<br><br>';
break;
}else{
$this->page_number = '';
$this->id_number = '';
$this->_controller = '404';
$this->_method = '404';
}
}
}
public function getController()
{
return $this->_controller;
}
public function getMethod()
{
return $this->_method;
}
public function getPageNumber()
{
return $this->page_number;
}
public function getIDNumber()
{
return $this->id_number;
}
/**
* Call our class and method from values in the URI
*/
public function dispatch()
{
if (file_exists('controller' . $this->_controller . '.php'))
{
include ('controller' . $this->_controller . '.php');
$controllerName = 'Controller' . $this->_controller;
$controller = new $controllerName($this->getIDNumber(),$this->getPageNumber());
$method = $this->_method;
if (method_exists($this->_controller, $this->_method))
{
return $controller->$method();
} else {
// method does not exist
}
} else {
// Controller does not exist
}
}
}
Run it
/**
* Testing the class
*/
$uri = isset($_GET['uri']) ? $_GET['uri'] : null;
$router = new Router($uri, $uri_route_map);
$router->dispatch();
?>
1) Look alright to me. The code looks a bit messy though.
2) Yes there is a better way. You're doing the regex because you want to match parts of the URL that you don't know. Why not do $parts = explode("/", $uri) then see if you can find the page you're looking for? You will need to define how many parameters you're expecting for each page or you wont know whether to pick forums with parameters array("viewform", 123) or forums/viewforum with parameters array(123).
explode feels loads better than a regex. It also adds the benefit of improved error handling. What if the argument passed to viewforum is not a number? Surely you can do better than "404" ;)
3) Make a seperate ajax handler. Ajax is hidden from view anyway so you don't need to bother with providing semantic URLs.
Example:
function find_route($parts) {
foreach ($uri_route_map as $route => $route_data) {
$route_check = implode("/", array_slice($parts, 0, count($parts) - $route_data['num_arguments']));
if ($route_check === $route) {
return $route_data;
}
}
throw new Exception("404?");
}
$uri = "forum/viewforum/522";
$parts = explode("/", $uri);
$route = find_route($parts);
$arguments = array_slice($parts, count($parts) - $route['num_arguments']);
$controller = $rRoute['controller'];
$method = $rRoute['method'];
$controller_instance = new $controller();
call_user_func_array(array($controller_instance, $method), $arguments);
(untested)
Plugins
Because of $uri_route_map you can't 'dynamically' register more plugins or pages or 'routes'. I'd add a function to add more routes dynamically to the Router.
Additionally you could consider some auto-discovery scheme that, for instance, will check the folder plugins/ for folders with a file called "manifest.php" that, when called, will optionally add more routes to Router.
1),2) I dont think that is good idea to put id_number and page_number in Router, because in future you can encounter many other parameters for url. Better just use controller and method and define in controller what to do with other parameters or create other class Request which deals with request info.
3) For ajax use url like ajax/module/action. And create ajax controller which do basic ajax security stuff, like checking against XSRF and then decides which controllers to run and action to call.
1) & 2) I will not say ,it's not right but why not using default routes ? Most of the time a route like
controller/action/param1/param2
is good enought for a majority of your page.
You probably could do something like that to define default routes :
$this->controller = 'index';
$this->action = 'index';
private function getDefaultRoutes()
{
$url = $_SERVER['REQUEST_URI'];
$tabUrl = explode('/',$url);
if(!empty($tabUrl))
{
$this->controller = array_shift($tabUrl);
$this->action = array_shift($tabUrl);
$this->params = $tabUrl;
}
}
And then if you need more specific routes you can define them in an array or whatever you want. In your routeryou just have to check if the current URI match a specific routes or the default routes.
By doing that you will decrease the number of routes to match and increase the speed of your router.
3) Your router is probably instancied by your index, without index no root, so unfortunately you probably can't avoid using it.
That why it's very important to avoid expensive action in your index. Typically don't init your database connection in index if all your pages don't need it.
Also please do not just tell me to use a framework
Don't forget to download some famous framework and look their code. It's the better way to learn. By doing that you will probably find a lot of good practices and answers.
1) So the main question, does anything just not look right?
Personally, I see this becoming more complicated as your site grows. An MVC framework, as I was taught, should pretty much be "Set it and forget it" – you're separating the request handler (controller) from the database querying and business end (model) and from the display elements (view).
[NB: You may need other core aspects. My standard framework includes some core elements that carry the session through the various parts, as well as handle fundamental aspects of the site work – For instance, while the models are responsible for making the right database calls as directed by the controller, there are core functions in a sql.class.php file that give me a standardized set of methods for making those calls and delivering or caching the results as needed.]
Your dispatch method is on the right track with this – you're extracting from the URI the name of the controller (Forums, Profiles, etc.). Do you need a uri map? I feel you're creating an unnecessary situation in which you have to update this map each time, rather than simply creating a new controller when you need new functionality, and registering it with the database. I'm not saying you're wrong per se, I just don't feel I'd have done it that way.
2) Is there a better way to detect what is in the URI than using the regex on an array like I am doing, consider it on a high traffic site?
Control the outcome (no pun intended, since it's the controller that does the work here). Consider this approach, and see how it works for you:
Your index.php file (aka "Main Controller") grabs the URI and explodes the values along "/" into bits. bit[0] is the controller ID – this says "I want to use the controller named bit[0]=>value". This is done as:
require_once( dirname( __FILE__ )."/controllers/".$bit[0]."controller.php" );
Personally, being a bit of a neat freak when it comes to directory structures, I use bit[0] to identify the directory in which controller.php is located, as I might have sub controllers.
It's this controller file that I use to parse other bits. For this, I'll use an example:
Assume that bit[0] carried the value "forums". I might pass, if it's set, bit[1] to a switch statement. By default, I always want to list, but I might specifically direct it to "list", "view", or "post" in bit[1]. This will tell me in the controller class which method to call. The method will then tell me to call the associated "forums" model if I need to perform queries and cache the forum listing, for instance.
The extraneous "bits" may do one of two things: they may be passed as simple arguments to the method as to what data to request from the model, or bit[1] may be complex enough to warrant a sub controller, and the subsequent bits will be passed to that controller to determine the appropriate action, as was done with the forums controller.
Regex, being slow, should be avoided when possible. Since we may have a URI of /forums/view/102305 we can assume that the forums controller will be passing 102305 to the method associated with the view argument (the method being something like private function displayPost( $id ) where $id is 102305). No regex is needed since we can simply explode the values along a common anticipated delimiter.
3) Since everything is routed through the index.php file with this, how would I go about handling AJAX requests?
Not terribly difficult. If the controller is set to, say, AJAX, you could rebuild the URL and direct access it. You could write exclusions in the .htaccess file (RewriteRule ^(AJAX)($|/) - [L]). Or (not ideal, but a sneaky workaround) is to append ../ to your AJAX URI to push the URI back to root – it's no longer attempting to access index.php so the rewrite rule doesn't apply.
Edit
Let's assume that we're using a URI of /forums/id-1234/page-4 per your example. Again, let's assume as I mentioned above that forums refers to the controller to be used, and every other / delimits arguments (what I like to call "drill downs"). So, in our forum controller file (let's call it forumcontroller.php, we might have something like this (extremely simplified) constructor:
// $registry is a class containing fundamental methods, and is meant to exemplify all
// classes tied to the main controller "index.php". Keep in mind, I'm assuming we've
// found the right controller by exploding the URI, and passed the remainder as bits
// to the constructor.
public function __construct( registry $registry ) {
$this->registry = $registry; //tying this controller to main controller.
// For ease and clarity, we're assuming there's no case in which you wouldn't have
// bits set. Error checking is easy.
$bits = $this->registry->getURLBits;
switch( $bits[0] ) {
case 'view': $this->showForumEntry( $bits[1], (isset( $bits[2] ) ? $bits[2] : '' ); break;
case 'edit': $this->editForumEntry( $bits[1] ); break;
case 'post': $this->postForumEntry(); break;
default: $this->listForumEntries(); break;
}
}
private function showForumEntry( $thread, $offset ) {
// Because you wanted to prepend id to the id element, we can use this for
// cheekiness in the query if our DB is well designed.
$data = explode('-', $thread);
// Select all from forums where id = 1234
$sql = "SELECT * FROM forums WHERE $data[0] = $data[1]";
if( $offset != '' ) {
$page = explode('-', $offset);
$offset = $page[1] * 25; // Or whatever your max per page is. Make it dynamic.
$max = $offset+25;
$sql .= " LIMIT $offset, $max";
}
// You see where I'm going with this...
}
The point is that you're in control of what is being passed and how it gets handled. Control the URIs and you can simplify their processing.
Edit 2
Reading through again, there's a few concepts that I think will help you and that you should familiarize yourself with:
View the "Factory" pattern here (My $registry is, at it's heart, a set of factories):
http://php.net/manual/en/language.oop5.patterns.php
A good breakdown of MVC graphically:
http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/images/mvc3.jpg
More on Factory methods:
http://www.devshed.com/c/a/PHP/Design-Patterns-in-PHP-Factory-Method-and-Abstract-Factory/
One other note, and this is a personal observation after having worked with Joomla, Drupal, Wordpress, and various different enterprise CMS and BBS solutions – Design solely with you in mind. When you start trying to become "something for everybody", you end up with a lot of unnecessary bloat that's getting loaded with each page and used 1 time out of 100. MVC is a design pattern, and using it as a pattern will tell you to get rid of excess in every aspect, including URIs. Processing /controller/arg1-Identifier/arg2-offset is unnecessary, and you can easily get away with /controller/id/offset (e.g. /forums/1234/4). If you want to make it SEO friendly, add the thread title, not a tag identifying the ID (e.g. /forums/1234-This-Is-A-Topic/4).
Now, let's also point out the obvious about my edit above – This is a controller intended solely for the forum element. Each element of your site (i.e. forums, galleries, profiles, etc.) should have it's own controller. Why? Because each is doing completely different things on its pages. So make use of that – you don't need to use a URI map so long as you understand that you're directing to the controller, and the controller is delegating responsibilities to the model and any sub controllers that might be needed.
I really hope that helps.
1) Is it works? If yes, then yes. Since above code only contain array, regex and validation for that, i dont think theres a problem(s) with your code. As long as it works. But if you ask, 'is that code is scalable?' then the answer would be various, and its all depend at your MVC Framework purposes (for example, is that framework for general uses, eg : blog, or its particullary for REST API provider. And so on...)
2) Yes. Kohana, Zend, CI and other popular (and highly optimized) PHP framework use that(array+regex on router).
3) I think you could just give it a flag in route block/section, and make that flag available as a global variable. So that, in your controller, you can decide which response to send for different request type (ajax/non-ajax) by checking that flag (for example you can provide $this->is_ajax as global method which available in Controller scope).
If I may add couple of points:
Remove id_number and page_number from the router - just pass everything that was matched to a controller, after all, it's a controller job to process that data, not router's
Don't pass $uri to a constructor, pass it to a dispatch() instead.
Why those isset()-s in $uri_route_map? Obviously they would be false, since $uri_route_map is defined before Router() object is instantiated.
Would recommend adding more logic to matching routine - in your current case sitename/forums will not match anything resulting in 404 (no trailing slash)
You can also define default parameters in your $uri_route_map, and then array_merge them with parameters matched. So, for example, when no page number is specified page_number will be equal to 1
If you are worried about perfomance on high traffic website, you can cache routes. After all, forums/viewforum/100 will always point to the same controller/method.
And why are you worried about sending AJAX requests to your index.php file? What's the problem with that?
im developing a web application, using multiple pages, each with their own controller.
The problem now is that there are some variables in a controller, created for one page, that are required for an other page ( with a different controller).
Therefor i need to load one controller into the other one.
I did this by adding
App::import('Controller', 'sections');
$sections= new sectionsController;
$sections->constructClasses();
to the controller, but this doens't seem to work..
Maybe u guys have some ideas?
Thnx in advance!
I think there're some misunderstandings in your mind about MVC architectural pattern.If you need some bullet for your gun,just get the bullet itself,and it's not neccessary to get another gun with it.So I hope you understand loading controller is really a bad idea.
Also if you want some variables accessable by all controllers,as Gaurav Sharma
mentioned ,you can use Configure::write() to store data in the application’s configuration app/config/core.php.e.g
Configure::write('somekey',$someval);
Then you can get $someval by Configure::read('somekey') in any controller.
You can use any one of the methods below to access a variable anywhere in the cakePHP application.
1.) use the configuration class OR
2.) use session for those variables
I've been working on this this morning. I actually get the controller name from a database, but I've changed it to use variables instead.
$controller_name = "Posts"; // the name of the controller.
$action = "index"; // The action we want to call.
App::import('Controller', $controller_name);
// Now we need the actual class name of the controller.
$controller_classname = $controller_name . 'Controller';
$Controller = new $controller_name;
$Controller->variable_name = "something"; // we can set class variables here too.
// Now invoke the dispatcher so that it will load and initialize everything (like components)
$d = new Dispatcher();
$d->_invoke($Controller, array('pass'=> '', 'action' => $action));
// And exit so it doesn't keep going.
exit(0);
I honestly didn't bother figuring out what 'pass' is for (I assume variables), but it throws a warning without it.
You will also need to explicitly call $this->render in your $action.