Zend Framework3 similar routes conflict - php

I'm using ZF3, in module.config.php file in Post module, I have one of these two routes,
'create-post' => [
'type' => Literal::class,
'options' => [
// Change this to something specific to your module
'route' => '/post/create',
'defaults' => [
'controller' => Controller\PostController::class,
'action' => 'create',
]
],
'may_terminate' => true,
'child_routes' => [
// You can place additional routes that match under the
// route defined above here.
],
],
'post' => [
'type' => Segment::class,
'options' => [
// Change this to something specific to your module
'route' => '/post[/:postId]',
'defaults' => [
'controller' => Controller\PostController::class,
'action' => 'show',
],
'constraints' => array(
'postId' => '\d{4}'
)
],
'may_terminate' => true,
'child_routes' => [
// You can place additional routes that match under the
// route defined above here.
],
]
Now when I visit http://localhost:8080/post/create it works, but when I visit http://localhost:8080/post/32, it doesn't work. It say 404 error, page not found.
Any help is much appreciated.

As per #jon Stirling comment on my question, I changed the constraints on post route and it worked.
Changed 'postId' => '\d{4}' to 'postId' => '\d{1,4}'
'post' => [
'type' => Segment::class,
'options' => [
// Change this to something specific to your module
'route' => '/post[/:postId]',
'defaults' => [
'controller' => Controller\PostController::class,
'action' => 'show',
],
'constraints' => array(
'postId' => '\d{1,4}'
)
],
'may_terminate' => true,
'child_routes' => [
// You can place additional routes that match under the
// route defined above here.
],
]

Related

ZF2 Routing Multiple Controllers based on route constraints

I'm refactoring some routing in a project and I'm trying to retain the current path structure of the following...
/events <-- Works
/events/super-bowl <-- Works
/events/2012-super-bowl <-- Doesn't work! Archive Layout
/events/2012-super-bowl/detail-page <-- Doesn't work! Archive sub layout
/events/2018-super-bowl <-- Works. Standard Layout, no sub layout
This is what I have tried...
'router' => [
'routes' => [
'events' => [
'type' => 'literal',
'options' => [
'route' => '/events',
'defaults' => [
'controller' => 'events',
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' => [
'super-bowl' => [
'type' => 'segment',
'options' => [
'route' => '/super-bowl',
'defaults' => [
'action' => 'superBowl',
],
],
],
'super-bowl-archives' => [
'type' => 'segment',
'options' => [
'route' => '/[:year]-super-bowl[/:detail]',
'constraints' => [
'year' => '^(2012|2013|2014)',
],
'defaults' => [
'controller' => 'super-bowl-archives',
'action' => 'index',
],
],
],
'super-bowl-standard' => [
'type' => 'segment',
'options' => [
'route' => '/[:year]-super-bowl',
'constraints' => [
'year' => '\d{4}'
],
'defaults' => [
'controller' => 'super-bowl-standard',
'action' => 'index',
],
],
],
],
],
],
],
I'm struggling with the archive layouts. I'd like to capture certain years and point them to a different controller. The archives have a sub route as well that I'm not sure how to achieve.
There are two problems in that configuration.
First: matching rule.
Since the matching isn't completely regex based, the correct "pattern" for year is '2012|2013|2014'
Second: matching order.
According to Zend router documentation:
Routes will be queried in a LIFO order, and hence the reason behind the name RouteStack
Last route will be matched first. Since 2012 is matched with \d{4}, you have to first test those "exceptions".
Simply put super-bowl-archives route after super-bowl-standard and it'll work
'router' => [
'routes' => [
'events' => [
'type' => 'literal',
'options' => [
'route' => '/events',
'defaults' => [
'controller' => 'events',
'action' => 'index'
]
],
'may_terminate' => true,
'child_routes' => [
'super-bowl' => [
'type' => 'segment',
'options' => [
'route' => '/super-bowl',
'defaults' => [
'action' => 'superBowl'
]
]
],
'super-bowl-standard' => [
'type' => 'segment',
'options' => [
'route' => '/[:year]-super-bowl',
'constraints' => [
'year' => '\d{4}'
],
'defaults' => [
'controller' => 'super-bowl-standard',
'action' => 'index'
]
]
],
'super-bowl-archives' => [
'type' => 'segment',
'options' => [
'route' => '/[:year]-super-bowl[/:detail]',
'constraints' => [
'year' => '2012|2013|2014'
],
'defaults' => [
'controller' => 'super-bowl-archives',
'action' => 'index'
]
]
]
]
]
]
],

Route path for different api version in zend framework 3

This is how I defined route for my api. It is prefixed with /api/v1. But now few new modules are added in api v2 and all v1 apis are remains same and available in v2. How can i modify this routes that will serve all routes belongs to /api/v1 and when /api/v1 is called and it should serve both /api/v2 and /api/v1 when /api/v2 is called?
module.config.php
'product' => array(
'type' => 'Zend\Router\Http\Segment',
'options' => array(
'route' => '/api/v1/categories[/:id]',
'defaults' => array(
'controller' => CategoryController::class,
),
),
),
'products' => array(
'type' => 'Zend\Router\Http\Segment',
'options' => array(
'route' => '/api/v1/products[/:id]',
'defaults' => array(
'controller' => ProductsController::class,
),
),
),
// ... at lots of v1 apis
//these are introduced in v2
'trends' => array(
'type' => 'Zend\Router\Http\Segment',
'options' => array(
'route' => '/api/v2/trends[/:id]',
'defaults' => array(
'controller' => TrendsController::class,
),
),
),
You can move those common v1 and v2 to a single parent route and v2-only ones to another. Below is sample (not tested) code that should help you understand the idea.
return [
// in Config.router.routes
'api' => [
'child_routes' => [
'v1' => [
'child_routes' => [
// your API 1-and-2 routes
'product' => [/* … */],
'products' => [/* … */]
],
'may_terminate' => false,
'options' => [
'constraints' => ['version' => 'v1|v2'],
'route' => '/:version'
],
'type' => Segment::class
],
'v2' => [
'child_routes' => [
// your API 2 routes
'trends' => [/* … */]
],
'may_terminate' => false,
'options' => ['route' => '/v2'],
'type' => Literal::class
]
],
'may_terminate' => false,
'options' => ['route' => '/api'],
'type' => Literal::class
]
];
If you prefer to not use child routes, you can simply add a route parameter/constraint instead of /v1:
return [
'product' => [
'options' => [
'constraints' => [
'id' => '…',
'version' => 'v1|v2'
],
'defaults' => ['controller' => CategoryController::class],
'route' => '/api/:version/categories[/:id]'
],
'type' => Segment::class
]
];
I know this is late, but I just found this question.
Whilst #gsc's answer is somewhat ok, this is not the correct answer.
This is the correct answer, and this is how I use it:
'api' => [
/** Our main route is /api **/
'may_terminate' => true,
'options' => ['route' => '/api'],
'type' => Literal::class,
'child_routes' => [
/** Since our main route is /api, this will become /api/v1/YOUR_ACTIONS **/
'v1' => [
'type' => Segment::class,
'options' => [
'route' => '/v1[/:action]',
'constraints' => [
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
],
'defaults' => [
'controller' => Controller\ApiV1Controller::class,
'action' => 'index',
],
],
],
/** Since our main route is /api, this will become /api/v2/YOUR_ACTIONS **/
'v2' => [
'type' => Segment::class,
'options' => [
'route' => '/v2[/:action]',
'constraints' => [
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
],
'defaults' => [
'controller' => Controller\ApiV2Controller::class,
'action' => 'index',
],
],
],
/** Add as many "versions" as you want, all with different controllers. **/
],
],
This allows you to use different "versions" of your controller and is shorter, better understandable and complies to standards.
Enjoy!

ZF3 optional parameter with child routes on Segment class doesn't want to match

I want to create routes with an optional [lang] parameter that would be used to set the application language as follows:
Example with default lang param(EN for example)
domain.tld/
domain.tld/users
domain.tld/contacts
etc.
Example witt new lang param(DE for examle)
domain.tld/de
domain.tld/de/users
domain.tld/de/contacts
etc.
This is my route configuration:
'router' => [
'routes' => [
'site' => [
'type' => Segment::class,
'options' => [
'route' => '[/:lang]',
'constraints' => [
'lang' => '[a-z]{2}',
],
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
'lang' => 'en',
],
],
'may_terminate' => true,
'child_routes' => [
'home' => [
'type' => Literal::class,
'options' => [
'route' => '/',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
'may_terminate' => true,
],
'application' => [
'type' => Segment::class,
'options' => [
'route' => '/application[/:action]',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
'may_terminate' => true,
],
],
],
],
]
When I have lang param in the url everything is fine. But when I try to open "default" lang without specifying it into the path (ex. example.tld/application/test) it gives me 404 error.
What I found is that the final regex after transformation from Segment class is (\G(?:/(?P<param1>([a-z]{2})))?) and path is /application/test. When preg_match is executed on Segment.php:385 it return match with following values:
[
'0' => '/ad',
'param1' => 'ad',
'1' => 'ad',
],
which is obviously the wrong behavior. My language is set to "ad" instead of open application/test action. I tested around 10 more regex but without success... (ex. ^[a-z]{2}$).
What I am doing wrong?
You get 404 because URL does not match site route, so child routes of site are skipped.
Try this:
'home' => [
'type' => Segment::class,
'options' => [
'route' => '/[:lang/]',
'constraints' => [
'lang' => '(en|de)?',
],
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
'lang' => 'en',
],
],
'may_terminate' => true,
'child_routes' => [
'application' => [
'type' => Segment::class,
'options' => [
'route' => 'application[/:action]',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
],
]
If you do not provide lang param then it takes default (en in my example) param.
Example:
example.tld/application/test
lang is en
controller is IndexController
action is test (testAction())
example.tld/de/application/test
lang is de
controller is IndexController
action is test (testAction())

Not getting default route parameter in ZF3

In ZF3 I want to get default parameter from route. I'm getting parameters in this way in controller:
$params = $this->params()->fromRoute('crud');
My urls looks like this:
1: somedomain/admin/color/add
2: somedomain/admin/color
In 1) I'm getting add in my $params variable.
In 2) I'm getting null but I'm expecting default (in this case view)
I think this is problem with bad router configuration.
'admin' => [
'type' => Segment::class,
'options' => [
'route' => '/admin/:action',
'defaults' => [
'controller' => Controller\AdminController::class,
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' => [
'color' => [
'type' => Segment::class,
'options' => [
'route' => '/:crud',
'constraints' => [
'crud' => 'add|edit|delete|view',
],
'defaults' => [
'controller' => Controller\AdminController::class,
'crud' => 'view',
],
],
],
],
],
In your route definition, you didn't says the router that your crud parameter is optionnal. So when you call somedomain/admin/color, it is the route /admin/:action which is selected.
To specify a optional parameter, use the bracket notation (assuming you use the same action):
'admin' => [
'type' => Segment::class,
'options' => [
'route' => '/admin/:action[/:crud]',
'defaults' => [
'controller' => Controller\AdminController::class,
'action' => 'index',
'crud' => 'view',
],
'constraints' => [
'crud' => 'add|edit|delete|view',
],
],
],

zf2 segment route catching id on last position

I want my url route to have a dynamic part and to end up on the same page not matter if whatever I have in the middle part of my URL.
E.g.:
/en/the-old-category/the-old-name/pid/123123123
/en/the-new-category/now-in-a-sub-category/the-new-name/pid/123123123
Should both get caught by the same controller/action (which will issue the pertinent 301/302 if necessary).
My current router contains:
'router' => [
'routes' => [
'blog' => [
'type' => 'segment',
'options' => [
'route' => "/[:language]",
'constraints' => [
'language' => '[a-z]{2}'
],
'defaults' => [
'controller' => 'Blog\Controller\List',
'action' => 'index',
'language' => 'en'
],
'may_terminate' => true,
'child_routes' => [
'detail' => 'segment',
'options' => [
'route' => '/:path/pid/:postid',
'constraints' => [
'path' => '.+',
'postid' => '\d{1,10}'
],
'defaults' => [
'controller' => 'Blog\Controller\List',
'action' => 'detail'
]
]
]
]
]
]
]
But it's not working.
/ and /en are being caught properly, but subroutes like the ones I proposed earlier, are not.
Am I in the right path to do what I want to do? Should I write a regex route instead?
.+ won't match / because segment routes split the path on / before applying constraints. To make your route work you'll need something like /:path/:foo/pid/:postid. A regex route might also work.
Since between the first and the last part of my URL I need to have a variable number of segments, I can't do this with a segment route, but I was able to manage it with a regex route, configured as follows:
'router' => [
'routes' => [
'blog' => [
'type' => 'regex',
'options' => [
'regex' => "/(?<language>[a-z]{2})?",
'spec' => "/%language%",
'defaults' => [
'controller' => 'Blog\Controller\List',
'action' => 'index'
],
'may_terminate' => true,
],
'child_routes' => [
'detail' => [
'type' => 'regex',
'options' => [
'regex' => '/(?<path>.+)/pid/(?<postid>\d+)$',
'spec' => '/%path%/pid/%postid%',
'defaults' => [
'controller' => 'Blog\Controller\List',
'action' => 'detail'
]
]
]
]
]
]
]

Categories