Feature #39740

Allow setting fixed values for route parts

Added by Andreas Wolf 10 months ago. Updated 18 days ago.

Status:Closed Start date:2012-08-12
Priority:Should have Due date:
Assignee:Bastian Waidelich % Done:

0%

Category:MVC - Routing
Target version:-
PHP Version: Complexity:
Has patch:No
Votes: 0

Description

In some situations, it is desirable to have routes that are only applied to certain sets of parameters, e.g. to have some subpackage in a completely different URI namespace:

api/v1/<controller>/<action> -> package MyPackage, subpackage Api <controller>/<action> -> package MyPackage

You could of course use the {@subpackage} key in the route URI here:

{@subpackage}/v1/{@controller}/{@action}

But as soon as you add a second subpackage, this will fail, because both subpackages would be matched by this route.

Setting "api" as the default value of @subpackage would also fail here, as all requests with a controller and an action would be matched by this route then, leading to errors for controllers that do not exist in the Api subpackage.

A solution for this is to allow fixed values for route parts, which are both

  1. matched when building the URI (so the route does not match if the $routeValues array passed to resolves() does not contain these fixed values)
  2. reconstructed when the URI is matched against the pattern (so they are available when resolving the controller and action)

I propose to add these fixed values to the routeParts array in the configuration, either as a simple value for the route part name or as the value of the "value" subkey:

 1 -
 2   name: "option A: fixed value as value of the route part name" 
 3   uriPattern: "api/v1/{@controller}/{@action}" 
 4   routeParts:
 5     '@package':    'MyPackage'
 6     '@subpackage': 'Api'
 7 
 8 -
 9 
10   name: "option B: fixed value in the 'value' key" 
11   uriPattern: "api/v1/{@controller}/{@action}" 
12   routeParts:
13     '@package':    
14       value:     'MyPackage'
15     '@subpackage':
16       value:     'Api'

I have implemented option A locally and will provide a patch after ironing out some glitches.


Related issues

related to TYPO3.Flow - Bug #43589: Route defaults can be omitted when creating URIs Resolved 2012-12-04

History

Updated by Andreas Wolf 10 months ago

Gnah, could somebody with the required permissions please fix the formatting?

Updated by Bastian Waidelich 6 months ago

  • Status changed from New to Needs Feedback
  • Assignee set to Bastian Waidelich

Hi Andreas,

sorry for the late reaction. I stumbled upon this issue several times but I never completely got it ;)

What's the difference between your example

1   -
2     name: "option A: fixed value as value of the route part name" 
3     uriPattern: "api/v1/{@controller}/{@action}" 
4     routeParts:
5       '@package':    'MyPackage'
6       '@subpackage': 'Api'

and

1   -
2     name: "option A: fixed value as value of the route part name" 
3     uriPattern: "api/v1/{@controller}/{@action}" 
4     defaults:
5       '@package':    'MyPackage'
6       '@subpackage': 'Api'

?

Updated by Andreas Wolf 6 months ago

The difference is that with defaults, these will be set if the value is not set at all. Therefore, all requests where no package/subpackage is set in the request will go to MyPackage\Api, which is not what I desire. I want it the other way round - only the requests to this subpackage have to match this route, no other requests. For them, I have a different URL scheme.

Updated by Bastian Waidelich 6 months ago

Andreas Wolf wrote:

I want it the other way round - only the requests to this subpackage have to match this route, no other requests.

Not sure whether I got this now, let me rephrase:
In your example you want the route to match only if the request path is "api/v1/<someController>/<someAction>" AND some other request argument (e.g. POST) is array('@package' => 'MyPackage', 'subpackage' => 'Api') ?
Or how would you specify those in your example?

Updated by Andreas Wolf 6 months ago

Bastian Waidelich wrote:

Andreas Wolf wrote:

I want it the other way round - only the requests to this subpackage have to match this route, no other requests.

Not sure whether I got this now, let me rephrase: In your example you want the route to match only if the request path is "api/v1/<someController>/<someAction>" AND some other request argument (e.g. POST) is array('@package' => 'MyPackage', 'subpackage' => 'Api') ?

No, I want all requests to /api/v1/.* to go to the Api subpackage. But I only want links to this subpackage to have a URL /api/v1/... This does not work if I set defaults, as all view helpers etc. where the package and/or subpackage is not set will then have these defaults applied - leading to errors because the controller is not found in the API subpackage.

Updated by Bastian Waidelich 6 months ago

Andreas Wolf wrote:

In your example you want the route to match only if the request path is "api/v1/<someController>/<someAction>" AND some other request argument (e.g. POST) is array('@package' => 'MyPackage', 'subpackage' => 'Api') ?

No, I want all requests to /api/v1/.* to go to the Api subpackage. But I only want links to this subpackage to have a URL /api/v1/... This does not work if I set defaults, as all view helpers etc. where the package and/or subpackage is not set will then have these defaults applied - leading to errors because the controller is not found in the API subpackage.

Thanks for your patience, now I'm with you!
I didn't get it at first, because the behavior you describe was meant to be the default.. That is: when creating an URI you have to set all values that are defined in the Routes default (except for @format which is a bit special)..

I don't know when this changed or if it was broken form the start, but right now you can omit default values that are not part of the uriPattern..
Changing Route::compareAndRemoveMatchingDefaultValues() from

 1 protected function compareAndRemoveMatchingDefaultValues(array $defaults, array &$routeValues) {
 2     foreach ($defaults as $key => $defaultValue) {
 3         if (isset($routeValues[$key])) {
 4             if (is_array($defaultValue)) {
 5                 if (!is_array($routeValues[$key])) {
 6                     return FALSE;
 7                 }
 8                 return $this->compareAndRemoveMatchingDefaultValues($defaultValue, $routeValues[$key]);
 9             } elseif (is_array($routeValues[$key])) {
10                 return FALSE;
11             }
12             if (strtolower($routeValues[$key]) !== strtolower($defaultValue)) {
13                 return FALSE;
14             }
15             unset($routeValues[$key]);
16         }
17     }
18     return TRUE;
19 }

to

 1 protected function compareAndRemoveMatchingDefaultValues(array $defaults, array &$routeValues) {
 2     foreach ($defaults as $key => $defaultValue) {
 3         if (!isset($routeValues[$key])) {
 4             if ($defaultValue === '' || ($key === '@format' && $defaultValue === 'html')) {
 5                 continue;
 6             }
 7             return FALSE;
 8         }
 9         if (is_array($defaultValue)) {
10             if (!is_array($routeValues[$key])) {
11                 return FALSE;
12             }
13             return $this->compareAndRemoveMatchingDefaultValues($defaultValue, $routeValues[$key]);
14         } elseif (is_array($routeValues[$key])) {
15             return FALSE;
16         }
17         if (strtolower($routeValues[$key]) !== strtolower($defaultValue)) {
18             return FALSE;
19         }
20         unset($routeValues[$key]);
21     }
22     return TRUE;
23 }

fixes this for me, but it would be quite a breaking change probably..
could you please verify if that works for you?

BTW: To make debugging with fluid easier you can replace line 263 of AbstractViewHelper from throw $exception; to return $exception->getMessage(); – we'll fix this in fluid asap

Updated by Bastian Waidelich 5 months ago

  • Status changed from Needs Feedback to Closed

This should be resolved with #43589 Please reopen/comment if that did not solve your issue

Updated by Andreas Wolf about 1 month ago

  • Status changed from Closed to New

I just upgraded our project to recent master and it seems we still have problems.

I've got this route configuration:


  name: 'Pools'
  uriPattern: 'admin/pools(/{@action})'
  defaults:
    '@package':    'MyPackageB'
    '@controller': 'Pool'
    '@action':     'index'

MyPackageB is a side package for some things that have nothing to do with my main package, so most routes go to the other package.

The problem here is that I have another route configured that matches a path like /admin/pools. Flow assumes that I want the controller "admin" from "MyPackageA", but that's plain wrong, because there is no such controller. Instead, the route given above should match.

Could it be a problem that the route given above is below the route that is used by Flow instead?

Updated by Bastian Waidelich about 1 month ago

  • Status changed from New to Needs Feedback

Andreas Wolf wrote:

Hi Andreas,

Could it be a problem that the route given above is below the route that is used by Flow instead?

Yes, sure, "The first route that matches, determines which action will be called with what parameters." – see http://docs.typo3.org/flow/TYPO3FlowDocumentation/TheDefinitiveGuide/PartIII/Routing.html#the-router

Can you please post the route that should not match but does?

Updated by Bastian Waidelich about 1 month ago

BTW: With

./flow routing:list

you can see in what order the routes are processed. Try including your "MyPackageB" routes before the "MyPackageA" routes or refine the routes to make them more specific

Updated by Bastian Waidelich 18 days ago

  • Status changed from Needs Feedback to Closed

Closing due to lack of feedback (feel free to re-open or comment if this issue is still relevant)

Also available in: Atom PDF