Variable Prefixed Routing in CakePHP - php

I'm creating an app in CakePHP which requires me to run 'multiple' apps within one CakePHP installation. Something like I have n controllers that behave the same for all applications, but they only differ when I call the database - anyway, I need to create a route which behaves something like this:
/app1/controller/action/a/b/c
/app2/controller/action/a/b/c
(where app1 and app2 are alphanumeric strings that can change to anything)
That would be routed to something like:
/controller/action/app1/a/b/c (or the same for app2, and so on)
The routed route could be just /controller/action/a/b/c too, but I need to have a way to access the app1 / app2 parts of the URL within the controller (for further processing within the controller). Is there a way to do this in CakePHP? Thanks.
Slightly related question: When the above is accomplished, is there a way to set a 'default' app-name (like when I attempt to access /controller/action/a/b/c it will automatically be routed to the equivalent of typing /global/controller/action/a/b/c?)
Thanks!
Effectively: What I want is just to use Routing (or any other CakePHP 'method' that can do this) to handle URLs like /foobar/controller/action/the/rest to /controller/action/the/rest and pass "foobar" to the controller, somehow. "Foobar" is any alphanumeric string.

In app/Config/routes.php add:
Router::connect(
'/:app/:controller/:action/*',
array(),
array( 'pass' => array( 'app' ))
);
This will pass the value of app as the first argument to the action in your controller. So in your controller you would do something like:
class FoosController Extends AppController {
public function view_something($app, $a, $b, $c) {
// ...
}
}
When you request /myApp1/foos/view_something/1/2/3 the value of $app would be 'myApp1', the value of $a would be 1, etc.
To connect other routes, before the above, you can add something like:
Router::connect(
'/pages/:action/*',
array( 'app' => 'global', 'controller' => 'pages' ),
array( 'pass' => array( 'app' )) // to make app 1st arg in controller
);

Instead of routing, you should use Model attribute -> dbconfig to change the databases dynamically. Also you should also have to send some arguments to the method by which you can identify which database needs to be connected with your application.

Related

CakePHP 3 make resource routes work with and without parameters

I have some code in my routes file:
Router::scope('/v1', function (RouteBuilder $routes) {
$routes->resources( 'Files');
});
And then a FilesController with a delete function like:
public function delete($id){
echo "here"; exit();
}
When I do:
DELETE http://192.168.1.197/v1/files/1
The response is here, however, if I do:
DELETE http://192.168.1.197/v1/files
The response is that it is missing the V1Controller.
What I would expect instead is for CakePHP to turn around and say "oops, you have passed the wrong number of required parameters".
Something very weird seems to be going on here and I am not quite sure what. How can I make the two do the same thing and point to the controller?
CakePHP operates very differently on exceptions when debug mode is enabled. When debug is true all exceptions are rendered with debug information, stack tracing and developer friendly messages.
When debug mode is false the exception is rendered as a standard HTTP response type. With handles for 400 and 500 error codes.
When the router can't find a match for a route there is no controller involved. The HTTP request never gets past the dispatching phase. It's the dispatcher that throws a 400 type exception.
In your given example the framework is throwing a MissingControllerException with the HTTP code of 404.
400 error codes are rendered via the ErrorController. CakePHP comes a with a default error controller, but if you generate a new application using the composer template, then you should have a default ErrorController in your app's controller holder.
In your templates there should be a src/Template/Error/error400.ctp file which displays the response for 400 codes. Keep in mind, that this template is not used when debug mode is enabled.
You can modify this template to find "closely" matching routes and offer them as recommendations to the user as feedback in the error message.
You can iterate all configured Routes easily like this:
foreach (Router::routes() as $route) {
$name = isset($route->options['_name']) ? $route->options['_name'] : $route->getName();
$output[] = [$name, $route->template, json_encode($route->defaults)];
}
Above taken from cakephp/src/Shell/RoutesShell.php:
Since this is technically a 404 error. There is no matching route and what you can do is try to find routes that are "close" to a match. The problem here is that you are subject to the same route matching challenges as the Router class.
The Router class uses dynamic routing techniques that take parts of the URL parameters and fills them in as names of controllers, names of actions and user defined parameters.
This can change significantly depending upon what kind of default router class you are using.
For example, you might be using the DashedRoute routing class which does the following:
/**
* This route class will transparently inflect the controller, action and plugin
* routing parameters, so that requesting `/my-plugin/my-controller/my-action`
* is parsed as `['plugin' => 'MyPlugin', 'controller' => 'MyController', 'action' => 'myAction']`
*/
class DashedRoute extends Route
You might instead be using the InflectedRoute routing class which does the following:
/**
* This route class will transparently inflect the controller and plugin routing
* parameters, so that requesting `/my_controller` is parsed as `['controller' => 'MyController']`
*/
class InflectedRoute extends Route
Since there are cases where routing could be using dynamic routing. It's not possible to know if a URL segment is a controller, action or named parameters.
Add to the complexity that you're also using a scoped segment named /v1 it becomes even more challenging to predict what the intended route is.
You can either create custom routes to catch these edge cases and render an informative error message, or you can try to add logic to the error400.ctp to display a more informative error message.
There is also a final option. Where CakePHP allows you to write your own custom Route classes, and/or modify the middleware with your own dispatcher.
I solved this another way entirely, just stop using the resource routes in CakePHP; I changed my code to:
$routes->get('/files/*', ['controller' => 'Files', 'action' => 'view'], 'files:get');
$routes->post('/files', ['controller' => 'Files', 'action' => 'add'], 'files:post');
$routes->put('/files/*', ['controller' => 'Files', 'action' => 'edit'], 'files:put');
$routes->patch('/files/*', ['controller' => 'Files', 'action' => 'view'], 'files:patch');
$routes->delete('/files/*', ['controller' => 'Files', 'action' => 'delete'], 'files:delete');
And it works exactly as how I wanted in the question...

Custom lithium routing scenario

I've been tasked with rewriting an existing website with large pre-existing link catalog. For argument's sake, let's assume we can't do anything that would change the link catalog. Here's a few examples of the link structure we're working with:
An item page would be:
www.domain.com/widgets/some-totally-awesome-large-purple-widget
A category sub page page would be:
www.domain.com/widgets/purple-widgets
A category parent page page would be:
www.domain.com/widgets/
A custom page may be:
www.domain.com/some-random-page
The various page types are too numerous to write individual Routers for.
Using Router::connect I can easily account for the first and second scenarios using something like:
Router::connect('/{:pageroot}/{:pagekey}', 'Pages::index');
In turn, the Pages::index method looks for entries in our database with the "key" of '/widgets/purple-widgets'.
However, the framework defaults to the '/{:controller}/{:action}/{:args}' route for pages like the third and fourth. I know that this is the correct behavior for the framework. Also, best practice would state that I should write the site to match this behavior. But, that isn't an option here.
What I need is a Router that would allow the third and fourth examples to function the same as the first. All examples should be sent to the Pages::index controller, which in turn queries a database using the URL path as a key.
If you don't have any convention in the URL for what is what, between page, item and category. I'd go with a very generic router.
Router::connect('/{:category}/{:page}/{:item}', 'Pages::any');
Router::connect('/{:category}/{:page}', array('Pages::any', 'item' => null));
Router::connect('/{:category}', array('Pages::any', 'page' => null, 'item' => null));
And in Pages::any() to search for the correct stuff. Is that category a page after all (example 4)? Is that page an item (example 1)?
or
You store the URL somewhere (e.g. a mapping table in the database) and use the pattern version of a lithium Route.
Router::connect(new Route(array(
'pattern' => '#^/(?<path>.+)$#',
'params' => array('controller' => 'pages', 'action' => 'any'),
'keys' => array('path' => 'path'),
// extra stuff, if the path is `tata`, it skips this route and uses
// any of the following ones that matches.
'handler' => function($request) {
if ($request->params['path'] == 'tata') {
return false;
} else {
return $request;
}
}
)));
From that point, you'll get the full URL.
You probably should write a smart Router Helper which is maybe able to process your request based on your db defined routes.
Take a look into: net/http/Router.php
especially connect(), parse() and match()
I would start to write some kind of anonymous function and progress it to a testable Class which is located in /extension.. ?

Write unit test for a controller that uses AuthComponent in CakePHP 2

I am trying to test a controller action that allows edition of user profiles. Among other things I want to test that every logged user can only edit its own profile and not other's. In case of breaking this restriction the action must redirect to a predefined home page.
With this scenario, I have a fixture that creates a user with ID = 1. So I was thinking on testing the restriction this way:
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data);
$this->testAction('/users/edit/2', array('method' => 'get'));
$url = parse_url($this->headers['Location']);
$this->assertEquals($url['path'], '/homepage');
The test passes this assert. So the next step is to check if executing '/users/edit/1', which has the ID of the logged user, shows the form:
$this->testAction('/users/edit/1', array('method' => 'get', 'return' => 'vars'));
$matcher = array(
'tag' => 'form',
'ancestor' => array('tag' => 'div'),
'descendant' => array('tag' => 'fieldset'),
);
$this->assertTag($matcher, $this->vars['content_for_layout'], 'The edition form was not found');
However this assert fails. After digging around with debug() I've found that $this->Auth->user() returns the whole information but $this->Auth->user('id') returns null. Since I use the latter in a comparison within the action, it evaluates as false and causes the
test to fail.
The curious thing is that it happens when testing but not when executing the action in a browser. So, what's the correct way of testing this action?
Thanks!
The actual correct answer should be using mock objects instead of actually login the user in manually:
$this->controller = $this->generate('Users', array(
'components' => array('Auth' => array('user')) //We mock the Auth Component here
));
$this->controller->Auth->staticExpects($this->once())->method('user') //The method user()
->with('id') //Will be called with first param 'id'
->will($this->returnValue(2)) //And will return something for me
$this->testAction('/users/edit/2', array('method' => 'get'));
Using mocks is the most easy way to test a controller, and also the most flexible one
Update 11 March 2015
You can also mock all method of AuthComponent
$this->controller = $this->generate('Users', array(
'components' => array('Auth') // Mock all Auth methods
));
I like Jose's answer, but when faced with a similar situation I want to use the actual AuthComponent and Session to create a test that would give me confidence.
I am using Controller-based authentication, which means that each controller in my app must provide its own isAuthorized() callback. I want to test MyController::isAuthorized(). It seems too easy to get a test to pass using mocks.
So ,instead of using TestCase::generate() to create a mock controller with mock components, I followed Mark Story's excellent article Testing CakePHP Controllers the hard way and provided my own mock controller that logs in a user with the the real CakePHP AuthComponent.
Here's my work. See the method testIsAuthorized() and the class def for MockAnnouncementsController near the top.
It seems to me that CakePHP testing framework assumes that you want to test controllers only through requestAction(). It was not designed to facilitate direct unit-testing of callback implementations like Controller::isAuthorized() within a controller without mocking the AuthComponent and perhaps other components, and that would give me less confidence in test of that particular method. Nevertheless, I think this is a valid use-case for unit-testing parts of a controller that are not actions (e.g. "index", "view"), but cannot be delegated to a component because they must be called by the core framework. I'm still thinking about how I could abstract it to make it available for any controller.
Instead of:
$this->Auth->user('id')
Try one of these:
$this->Auth->data['User']['id']
$this->Session->read('Auth.User.id')
Set it like so:
$this->Users->Session->write('Auth.User',
array('id' => 1,'and_other_fields_you_need' => 'whatever')
);
Mark Story gives me the answer in a CakePHP ticket. Basically I have to log the user like this:
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data['User']);
instead of
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data);

Turn off default routes in Kohana 3?

I believe I know how to do this, but wanted to verify with my awesome community peeps. =)
Here's an example:
I have a Controller class called 'tami', with an action 'index'.
I know that if I want someone to access that controller/action combo via an URL other than "/tami/" or "/tami/index", then I should add a route, via something like this:
Route::set('pretty_tami', 'these-are-my-initials(/<action>)')
->defaults(array(
'controller' => 'tami',
'action' => 'index',
));
But, users can still access this page via /tami/.
How can I turn off the default routing, so that the only valid routes are the ones I define?
I assume I can just remove the default route found in kohana/application/bootstrap.php. Is that correct? Or would that break something else?
I'd say exactly the same as #simshaun — either remove the default route (leaving other controllers unreachable) or check in the before() function in Controller_Tami for the uri to see if it's what you're after.
If you're using Kohana 3.1, you can now use lambda logic/anonymous functions to define your routes.
Something like this would take the extra routing logic out of the controller (which is good as we're keeping it in one place):
Route::set('default', function($uri)
{
if ($uri == 'tami' OR $uri == 'tami/index')
{
// Route not allowed by the default methods
throw new Kohana_404_Exception("Route not permitted");
}
},
'(<controller>(/<action>(/<id>)))'
);
Something I haven't yet used but it looks amazingly powerful.
I think the easiest way would be to remove the default route in your bootstrap file, yes. However, any controllers that you have not manually specified a route for can no longer be accessed.
What I would do is create a class, e.g. Controller_Derouter that Controller_Tami extends. Use the before() method in Controller_Derouter to test if the controller was accessed from the default route, and if so, throw a 404. I think you should be able to do that by comparing $this->request->controller against the first URI segment.
Edit: The solution mentioned above is unnecessary if you ever only plan on disabling the default route for just the Tami controller. If that's the case, you could just implement the before() method directly in the Tami controller.
Maybe like this?
Route::set('pretty_tami', 'these-are-my-initials/<action>')
->defaults(array(
'controller' => 'tami',
));
So there wouldn't be a default action. And you probably want to update the default route (if you still have one) with a regex to exclude tami.
Route::set('default', '(<controller>(/<action>(/<id>)))', array('controller' => '/^(?!tami)/'))
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));

PHP Application URL Routing

So I'm writing a framework on which I want to base a few apps that I'm working on (the framework is there so I have an environment to work with, and a system that will let me, for example, use a single sign-on)
I want to make this framework, and the apps it has use a Resource Oriented Architecture.
Now, I want to create a URL routing class that is expandable by APP writers (and possibly also by CMS App users, but that's WAYYYY ahead in the future) and I'm trying to figure out the best way to do it by looking at how other apps do it.
I prefer to use reg ex over making my own format since it is common knowledge. I wrote a small class that I use which allows me to nest these reg ex routing tables. I use to use something similar that was implemented by inheritance but it didn't need inheritance so I rewrote it.
I do a reg ex on a key and map to my own control string. Take the below example. I visit /api/related/joe and my router class creates a new object ApiController and calls it's method relatedDocuments(array('tags' => 'joe'));
// the 12 strips the subdirectory my app is running in
$index = urldecode(substr($_SERVER["REQUEST_URI"], 12));
Route::process($index, array(
"#^api/related/(.*)$#Di" => "ApiController/relatedDocuments/tags",
"#^thread/(.*)/post$#Di" => "ThreadController/post/title",
"#^thread/(.*)/reply$#Di" => "ThreadController/reply/title",
"#^thread/(.*)$#Di" => "ThreadController/thread/title",
"#^ajax/tag/(.*)/(.*)$#Di" => "TagController/add/id/tags",
"#^ajax/reply/(.*)/post$#Di"=> "ThreadController/ajaxPost/id",
"#^ajax/reply/(.*)$#Di" => "ArticleController/newReply/id",
"#^ajax/toggle/(.*)$#Di" => "ApiController/toggle/toggle",
"#^$#Di" => "HomeController",
));
In order to keep errors down and simplicity up you can subdivide your table. This way you can put the routing table into the class that it controls. Taking the above example you can combine the three thread calls into a single one.
Route::process($index, array(
"#^api/related/(.*)$#Di" => "ApiController/relatedDocuments/tags",
"#^thread/(.*)$#Di" => "ThreadController/route/uri",
"#^ajax/tag/(.*)/(.*)$#Di" => "TagController/add/id/tags",
"#^ajax/reply/(.*)/post$#Di"=> "ThreadController/ajaxPost/id",
"#^ajax/reply/(.*)$#Di" => "ArticleController/newReply/id",
"#^ajax/toggle/(.*)$#Di" => "ApiController/toggle/toggle",
"#^$#Di" => "HomeController",
));
Then you define ThreadController::route to be like this.
function route($args) {
Route::process($args['uri'], array(
"#^(.*)/post$#Di" => "ThreadController/post/title",
"#^(.*)/reply$#Di" => "ThreadController/reply/title",
"#^(.*)$#Di" => "ThreadController/thread/title",
));
}
Also you can define whatever defaults you want for your routing string on the right. Just don't forget to document them or you will confuse people. I'm currently calling index if you don't include a function name on the right. Here is my current code. You may want to change it to handle errors how you like and or default actions.
Yet another framework? -- anyway...
The trick is with routing is to pass it all over to your routing controller.
You'd probably want to use something similar to what I've documented here:
http://www.hm2k.com/posts/friendly-urls
The second solution allows you to use URLs similar to Zend Framework.
Use a list of Regexs to match which object I should be using
For example
^/users/[\w-]+/bookmarks/(.+)/$
^/users/[\w-]+/bookmarks/$
^/users/[\w-]+/$
Pros: Nice and simple, lets me define routes directly
Cons: Would have to be ordered, not making it easy to add new things in (very error prone)
This is, afaik, how Django does it
I think a lot of frameworks use a combination of Apache's mod_rewrite and a front controller. With mod_rewrite, you can turn a URL like this: /people/get/3 into this:
index.php?controller=people&method=get&id=3. Index.php would implement your front controller which routes the page request based on the parameters given.
As you might expect, there are a lot of ways to do it.
For example, in Slim Framework , an example of the routing engine may be the folllowing (based on the pattern ${OBJECT}->${REQUEST METHOD}(${PATTERM}, ${CALLBACK}) ):
$app->get("/Home", function() {
print('Welcome to the home page');
}
$app->get('/Profile/:memberName', function($memberName) {
print( 'I\'m viewing ' . $memberName . '\'s profile.' );
}
$app->post('/ContactUs', function() {
print( 'This action will be fired only if a POST request will occure');
}
So, the initialized instance ($app) gets a method per request method (e.g. get, post, put, delete etc.) and gets a route as the first parameter and callback as the second.
The route can get tokens - which is "variable" that will change at runtime based on some data (such as member name, article id, organization location name or whatever - you know, just like in every routing controller).
Personally, I do like this way but I don't think it will be flexible enough for an advanced framework.
Since I'm working currently with ZF and Yii, I do have an example of a router I've created as part of a framework to a company I'm working for:
The route engine is based on regex (similar to #gradbot's one) but got a two-way conversation, so if a client of yours can't run mod_rewrite (in Apache) or add rewrite rules on his or her server, he or she can still use the traditional URLs with query string.
The file contains an array, each of it, each item is similar to this example:
$_FURLTEMPLATES['login'] = array(
'i' => array( // Input - how the router parse an incomming path into query string params
'pattern' => '#Members/Login/?#i',
'matches' => array( 'Application' => 'Members', 'Module' => 'Login' ),
),
'o' => array( // Output - how the router parse a query string into a route
'#Application=Members(&|&)Module=Login/?#' => 'Members/Login/'
)
);
You can also use more complex combinations, such as:
$_FURLTEMPLATES['article'] = array(
'i' => array(
'pattern' => '#CMS/Articles/([\d]+)/?#i',
'matches' => array( 'Application' => "CMS",
'Module' => 'Articles',
'Sector' => 'showArticle',
'ArticleID' => '$1' ),
),
'o' => array(
'#Application=CMS(&|&)Module=Articles(&|&)Sector=showArticle(&|&)ArticleID=([\d]+)#' => 'CMS/Articles/$4'
)
);
The bottom line, as I think, is that the possibilities are endless, it just depend on how complex you wish your framework to be and what you wish to do with it.
If it is, for example, just intended to be a web service or simple website wrapper - just go with Slim framework's style of writing - very easy and good-looking code.
However, if you wish to develop complex sites using it, I think regex is the solution.
Good luck! :)
You should check out Pux https://github.com/c9s/Pux
Here is the synopsis
<?php
require 'vendor/autoload.php'; // use PCRE patterns you need Pux\PatternCompiler class.
use Pux\Executor;
class ProductController {
public function listAction() {
return 'product list';
}
public function itemAction($id) {
return "product $id";
}
}
$mux = new Pux\Mux;
$mux->any('/product', ['ProductController','listAction']);
$mux->get('/product/:id', ['ProductController','itemAction'] , [
'require' => [ 'id' => '\d+', ],
'default' => [ 'id' => '1', ]
]);
$mux->post('/product/:id', ['ProductController','updateAction'] , [
'require' => [ 'id' => '\d+', ],
'default' => [ 'id' => '1', ]
]);
$mux->delete('/product/:id', ['ProductController','deleteAction'] , [
'require' => [ 'id' => '\d+', ],
'default' => [ 'id' => '1', ]
]);
$route = $mux->dispatch('/product/1');
Executor::execute($route);
Zend's MVC framework by default uses a structure like
/router/controller/action/key1/value1/key2/value2
where router is the router file (mapped via mod_rewrite, controller is from a controller action handler which is defined by a class that derives from Zend_Controller_Action and action references a method in the controller, named actionAction. The key/value pairs can go in any order and are available to the action method as an associative array.
I've used something similar in the past in my own code, and so far it's worked fairly well.
Try taking look at MVC pattern.
Zend Framework uses it for example, but also CakePHP, CodeIgniter, ...
Me personally don't like the MVC model, but it's most of the time implemented as "View for web" component.
The decision pretty much depends on preference...

Categories