Skip to content

Commit

Permalink
Merge pull request #578 from kohana/3.4/feature/load-controller-by-fqcn
Browse files Browse the repository at this point in the history
Load controllers with FQCN.
  • Loading branch information
rjd22 committed Jan 12, 2015
2 parents 840cb98 + f3a2081 commit 43153da
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 21 deletions.
6 changes: 6 additions & 0 deletions classes/Kohana/Request/Client/Internal.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public function execute_request(Request $request, Response $response)
$prefix .= str_replace(array('\\', '/'), '_', trim($directory, '/')).'_';
}

// Use the FQCN to load the Controller
if (substr($controller, 0, 1) === '\\')
{
$prefix = '';
}

if (Kohana::$profiling)
{
// Set the benchmark name
Expand Down
8 changes: 8 additions & 0 deletions classes/Kohana/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,14 @@ public function defaults(array $defaults = NULL)
return $this->_defaults;
}

if (isset($defaults['controller']) AND substr($defaults['controller'], 0, 1) === '\\')
{
if (isset($defaults['directory']))
{
throw new Kohana_Exception('Route directory should not be set when the controller is a FQCN.');
}
}

$this->_defaults = $defaults;

return $this;
Expand Down
20 changes: 20 additions & 0 deletions guide/kohana/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ If a key in a route is optional (or not present in the route), you can provide a

[!!] The `controller` and `action` key must always have a value, so they either need to be required in your route (not inside of parentheses) or have a default value provided.

[!!] When using the `Fully Qualified Class Name` or `FQCN` for the `controller` you aren't allowed to set `directory`. The reason is that when using the `FQCN` the autoloader will look for the right location of the file.

[!!] Kohana automatically converts controllers to follow the standard naming convention. For example /blog/view/123 would look for the controller Controller_Blog in classes/Controller/Blog.php and trigger the action_view() method on it.

In the default route, all the keys are optional, and the controller and action are given a default. If we called an empty url, the defaults would fill in and `Controller_Welcome::action_index()` would be called. If we called `foobar` then only the default for action would be used, so it would call `Controller_Foobar::action_index()` and finally, if we called `foobar/baz` then neither default would be used and `Controller_Foobar::action_baz()` would be called.
Expand All @@ -80,6 +82,24 @@ TODO: example of either using directory or controller where it isn't in the rout

### Directory

### Selecting controllers by their FQCN

You can also use the `Fully Qualified Class Name` to find the controller. It will enable you to store your controllers at the location of your choosing as long as the autoloader is able to find it.

When using `FQCN` controllers the controllers will not use any prefixed or affixes so `\Acme\Module\Controller\Demo` will point to `class Demo {}` and `\Acme\Module\Controller\DemoController` will point to `class DemoController {}`. You will have full flexibility on how you name you classes.

Because of the autoloading the `directory` default value will not work and setting it will result in a `Exception` being thrown.

An example of a `FQCN` route:

/*
* Routes using FQCN
*/
Route::set('default', 'controller-name/<action>')
->defaults(array(
'controller' => '\Acme\Module\Controller\DemoController'
));

## Route Filters

In 3.3, you can specify advanced routing schemes by using filter callbacks. When you need to match a route based on more than just the URI of a request, for example, based on the method request (GET/POST/DELETE), a filter will allow you to do so. These filters will receive the `Route` object being tested, the currently matched `$params` array, and the `Request` object as the three parameters. Here's a simple example:
Expand Down
18 changes: 18 additions & 0 deletions tests/kohana/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,24 @@ public function test_defaults_are_not_used_if_param_is_identical()
$this->assertSame('welcome2', $route->uri(array('controller' => 'welcome2')));
}

/**
* When using a FQCN test if the directory is not set else throw an exception
*
* @test
* @covers Route::defaults
*/
public function test_defaults_throws_exception_when_setting_fqcn_and_directory()
{
$this->setExpectedException('Kohana_Exception', 'Route directory should not be set when the controller is a FQCN.');

$route = new Route('(<controller>(/<action>(/<id>)))');
$route->defaults(array(
'directory' => 'directory/path',
'controller' => '\FQCN\Class\Name',
'action' => 'index'
));
}

/**
* Provider for test_required_parameters_are_needed
*
Expand Down
129 changes: 108 additions & 21 deletions tests/kohana/request/client/InternalTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?php
namespace {
use test\RequestClientInternalTest\ControllerCapturingResponseStub;
use test\RequestClientInternalTest\FixedParametersRequestStub;

/**
* Unit tests for internal request client
Expand Down Expand Up @@ -36,33 +39,117 @@ public function provider_response_failure_status()
*/
public function test_response_failure_status($directory, $controller, $action, $uri, $expected)
{
// Mock for request object
$request = $this->getMock('Request', array('directory', 'controller', 'action', 'uri', 'response'), array($uri));
$request = new FixedParametersRequestStub(array(
'directory' => $directory,
'controller' => $controller,
'action' => $action,
'uri' => $uri,
));

$request->expects($this->any())
->method('directory')
->will($this->returnValue($directory));
$internal_client = new Request_Client_Internal;
$response = $internal_client->execute($request);
$this->assertSame($expected, $response->status());
}

$request->expects($this->any())
->method('controller')
->will($this->returnValue($controller));
/**
* @return array
*/
public function provider_controller_class_mapping()
{
return array(
array(
array('controller' => 'RequestClientInternalTestControllerDummy', 'directory' => ''),
'Controller_RequestClientInternalTestControllerDummy',
),
array(
array('controller' => 'ControllerDummy', 'directory' => 'RequestClientInternalTest'),
'Controller_RequestClientInternalTest_ControllerDummy',
),
array(
array('controller' => '\RequestClientInternalTestControllerDummy', 'directory' => ''),
'\RequestClientInternalTestControllerDummy',
),
array(
array('controller' => '\test\RequestClientInternalTest\ControllerDummy', 'directory' => ''),
'\test\RequestClientInternalTest\ControllerDummy',
),
);
}

$request->expects($this->any())
->method('action')
->will($this->returnValue($action));
/**
* @param array $request_params
* @param string $expect_class
*
* @dataProvider provider_controller_class_mapping
*/
public function test_maps_request_params_to_controller_class($request_params, $expect_class)
{
$client = new Request_Client_Internal;
$request = new FixedParametersRequestStub($request_params);
$response = new ControllerCapturingResponseStub;
$client->execute_request($request, $response);

$request->expects($this->any())
->method('uri')
->will($this->returnValue($uri));
$this->assertInstanceOf($expect_class, $response->getController());
}
}

$request->expects($this->any())
->method('response')
->will($this->returnValue($this->getMock('Response')));
class RequestClientInternalTestControllerDummy extends Controller {

$internal_client = new Request_Client_Internal;
public function execute()
{
if ($this->response instanceof ControllerCapturingResponseStub)
{
$this->response->setController($this);
}
return $this->response;
}

$response = $internal_client->execute($request);
}

class Controller_RequestClientInternalTest_ControllerDummy extends \RequestClientInternalTestControllerDummy {}
class Controller_RequestClientInternalTestControllerDummy extends \RequestClientInternalTestControllerDummy {}

} // End of global namespace

namespace test\RequestClientInternalTest {

class FixedParametersRequestStub extends \Request {

public function __construct($params)
{
$params = array_merge(
array(
'directory' => '',
'controller' => '',
'action' => 'index',
'uri' => '/',
),
$params
);
$this->_directory = $params['directory'];
$this->_controller = $params['controller'];
$this->_action = $params['action'];
$this->_uri = $params['uri'];
}

$this->assertSame($expected, $response->status());
}
}

class ControllerCapturingResponseStub extends \Response {

protected $controller;

public function __construct() {}

public function setController(\Controller $controller)
{
$this->controller = $controller;
}

public function getController()
{
return $this->controller;
}
}

class ControllerDummy extends \RequestClientInternalTestControllerDummy {}
}

0 comments on commit 43153da

Please sign in to comment.