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?
I am currently working on CMS for a client, and I am going to be using Codeigniter to build on top of, it is only a quick project so I am not looking for a robust solution.
To create pages, I am getting to save the page details and the pull the correct page, based on the slug matching the slug in the mysql table.
My question is however, for this to work, I have to pass this slug from the URL the controller then to the model, this means that I also have too have the controller in the URL which I do not want is it possible to remove the controller from the URL with routes?
so
/page/our-story
becomes
/our-story
Is this possible
I would recommend to do it this way.
Let's say that you have : controller "page" / Method "show"
$route['page/show/:any'] = "$1";
or method is index which I don't recommend, and if you have something like news, add the following.
$route['news/show/:any'] = "news/$1";
That's it.
Yes, certainly. I just recently built a Codeigniter driven CMS myself. The whole purpose of routes is to change how your urls look and function. It helps you break away from the controller/function/argument/argument paradigm and lets you choose how you want your url's to look like.
Create a pages controller in your controllers directory
Place a _remap function inside of it to catch all requests to the controller
If you are using the latest version of CI 2.0 from Bitbucket, then in your routes.php file you can put this at the bottom of the file: $routes['404_override'] = "pages"; and then all calls to controllers that don't exist will be sent to your controller and you then can check for the presence of URL chunks. You should also make pages your default controller value as well.
See my answer for a similar question here from a few months back for example code and working code that I use in my Codeigniter CMS.
Here's the code I used in a recent project to achieve this. I borrowed it from somewhere; can't remember where.
function _remap($method)
{
$param_offset = 2;
// Default to index
if ( ! method_exists($this, $method))
{
// We need one more param
$param_offset = 1;
$method = 'index';
}
// Since all we get is $method, load up everything else in the URI
$params = array_slice($this->uri->rsegment_array(), $param_offset);
// Call the determined method with all params
call_user_func_array(array($this, $method), $params);
}
Then, my index function is where you would put your page function.
I was thinking about implementing a logic similar to observer pattern on my website, for implementing hooks.
What I am looking for is something similar to this Best way to allow plugins for a PHP application
However the code there is too limited, as I cant attach multiple hooks to same listener.
I am clueless about how to amplify that code to make it able to listen multiple actions at one event.
Thank You
You can do as ircmaxell suggests: add hooks. But clearly, the information he gave was not enough for you.
If you like learning by example, you may look at the CMS Drupal, wich is not OOP, but uses the observer pattern, called hooks all over the place to allow a modular design.
A hook works as follows:
a piece of php looks for the existence of a specially named function.
If that exists, call it and use its output (or do nothing with it)
For example:
Just before an article gets saved in Drupal, the article-system calls the hook_insert
Every module that has a function in the name of ModuleName_insert, will see that function being called. Example: pirate.module may have a function pirate_insert(). The article system makes a roundtrip along all the modules and sees if ModuleName_insert exists. It will pass by pirate module and finds pirate_insert(). It will then call that function (and pass some arguments along too). As such, allowing the pirate.module to change the article just before insertation (or fire some actions, such as turning the body-text into pirate-speek).
The magic happens in so called user_callbacks. An example:
$hook = 'insert'
foreach (module_implements($hook) as $module) {
$function = $module .'_'. $hook;
$result = call_user_func_array($function, $args);
}
And the function module_implements might look something like:
$list = module_list(FALSE, TRUE, $sort); //looks for files that are considered "modules" or "addons".
foreach ($list as $module) {
if (function_exists($module.'_'.$hook)) { //see if the module has the hook 'registered'
$implementations[$hook][] = $module; //if so: add it to a list with functions to be called.
}
}
Simply add a ' * ' hook, and modify the hook() function to call all the 'hooks' in both the named event and the ' * ' event.
Then, simply do:
add_listener('*', 'mycallback');
Take a look at Spl_Observer.
You said you didn't want OOP, but you can easily implement a non-OOP wrapper around this.
In most of the tutorials for PHP MVC design structures, a router class is used to take user input and look up the right controller in order to process that input accurately. The input normally takes the form of a url. For example http://example.com/foo/bar/ would ask the router to find the controller named foo and fire the method bar.
PHP also has an auto-include function which requires a consistent naming system to used for your classes. For example, if I wanted to get an instance of the class Foo, I would need the file to be called Foo.php and inside would be a class called Foo.
Unfortunately, in tandem, these mechanisms place competing demands on the naming system. The url is very 'front facing' and as such, clients will often need it to reflect the content of a particular page. For example, if I have a page that gives directions to a venue, the client may need this page to be at http://example.com/venues/directions/. Later the content may change and the number of venues is reduced to 1 and now the client wishes the url to read http://example.com/venue/directions/. Obviously, this is a very trivial example but I think the need to change urls occasionally is clear.
Since the urls are very tightly connected to the controller classes, a change to a url means the class file name, the class name itself and any instances of that class will all require changing. For more complex systems this seems very time consuming, to me.
What sort of solutions are there to this problem? It seems to me that a directory of sorts will be necessary in which relations between urls and controllers are stored such that any url could be used to call any controller regardless of what either is named. Thus we would have a venues controller and a venues url and when the client requests a change in the url, we just make a change in the directory translating the new "venue" into "venues".
I think the best way to do this would be in a database table but this then requires an extra query for every user request. Is a database the right way to implement a directory? Is a directory even the best way to solve the above problem? Is this problem even a problem in the first place???
Some solutions I have seen to this is to explicitly specify a routing "connection" to a controller/action.
For example, in NiceDog, you can specify routes like
R('venues?/directions')
->controller('VenueController')
->action('directionsAction')
->on('GET');
Allowing a regular expression to match the URL. (The expression above will match venue/directions or venues/directions) I like this method, because it decouples the controller naming scheme from the URL naming scheme.
Another more configuration-based approach would be to have some kind of explicit mapping:
$routes = array(
'venues?' =>
array('controller'=>'VenueController','action'=>'indexAction'),
'venues?/directions' =>
array('controller'=>'VenueController','action'=>'directionsAction')
);
And then a simple dispatch function:
function dispatch($url,$routes) {
foreach ($routes as $pattern=>$map) {
if (preg_match($pattern,$url,$matches)) {
$controller = new $map['controller']();
$action = $map['action'];
call_user_func_array(
array($controller,$action),
array_slice($matches,1)
);
return true;
}
}
throw new Exception('Route not found.');
}
A good way to solve this problem is for the MVC framework to allow you to specify your own routing rules. For example, see URI Routing in the CodeIgniter framework's documentation.
To go along with your example, this would allow you to remap requests for /venues/ to /venue.