Attention
TYPO3 v10 has reached end-of-life as of April 30th 2023 and is no longer being maintained. Use the version switcher on the top left of this page to select documentation for a supported version of TYPO3.
Need more time before upgrading? You can purchase Extended Long Term Support (ELTS) for TYPO3 v10 here: TYPO3 ELTS.
Collection of various routing examples¶
Note
If you have additional examples and are willing to share, please create a Pull Request on Github and add it to this page.
EXT: News¶
Prerequisites:
The plugins for list view and detail view are on separate pages. If you use the category menu or tag list plugins to filter news records, their titles (slugs) are used.
Result:
Detail view:
https://example.org/news/detail/the-news-title
Pagination:
https://example.org/news/page-2
Category filter:
https://example.org/news/my-category
Tag filter:
https://example.org/news/my-tag
1routeEnhancers:
2 News:
3 type: Extbase
4 extension: News
5 plugin: Pi1
6 routes:
7 - routePath: '/page-{page}'
8 _controller: 'News::list'
9 _arguments:
10 page: '@widget_0/currentPage'
11 - routePath: '/{news-title}'
12 _controller: 'News::detail'
13 _arguments:
14 news-title: news
15 - routePath: '/{category-name}'
16 _controller: 'News::list'
17 _arguments:
18 category-name: overwriteDemand/categories
19 - routePath: '/{tag-name}'
20 _controller: 'News::list'
21 _arguments:
22 tag-name: overwriteDemand/tags
23 defaultController: 'News::list'
24 defaults:
25 page: '0'
26 aspects:
27 news-title:
28 type: PersistedAliasMapper
29 tableName: tx_news_domain_model_news
30 routeFieldName: path_segment
31 page:
32 type: StaticRangeMapper
33 start: '1'
34 end: '100'
35 category-name:
36 type: PersistedAliasMapper
37 tableName: sys_category
38 routeFieldName: slug
39 tag-name:
40 type: PersistedAliasMapper
41 tableName: tx_news_domain_model_tag
42 routeFieldName: slug
For more examples and background information see News manual.
EXT: Blog with custom Aspect¶
Taken from https://typo3.com routing configuration and the blog extension.
Archive¶
1routeEnhancers:
2 BlogArchive:
3 type: Extbase
4 extension: Blog
5 plugin: Archive
6 routes:
7 -
8 routePath: '/{year}'
9 _controller: 'Post::listPostsByDate'
10 _arguments:
11 year: year
12 -
13 routePath: '/{year}/page-{page}'
14 _controller: 'Post::listPostsByDate'
15 _arguments:
16 year: year
17 page: '@widget_0/currentPage'
18 -
19 routePath: '/{year}/{month}'
20 _controller: 'Post::listPostsByDate'
21 _arguments:
22 year: year
23 month: month
24 -
25 routePath: '/{year}/{month}/page-{page}'
26 _controller: 'Post::listPostsByDate'
27 _arguments:
28 year: year
29 month: month
30 page: '@widget_0/currentPage'
31 defaultController: 'Post::listPostsByDate'
32 requirements:
33 year: '[0-9]{1..4}'
34 month: '[a-z]+'
35 page: '\d+'
36 aspects:
37 year:
38 type: BlogStaticDatabaseMapper
39 table: 'pages'
40 field: 'crdate_year'
41 groupBy: 'crdate_year'
42 where:
43 doktype: 137
44 month:
45 type: StaticValueMapper
46 map:
47 january: 1
48 february: 2
49 march: 3
50 april: 4
51 may: 5
52 june: 6
53 july: 7
54 august: 8
55 september: 9
56 october: 10
57 november: 11
58 december: 12
59 localeMap:
60 -
61 locale: 'de_.*'
62 map:
63 januar: 1
64 februar: 2
65 maerz: 3
66 april: 4
67 mai: 5
68 juni: 6
69 juli: 7
70 august: 8
71 september: 9
72 oktober: 10
73 november: 11
74 dezember: 12
75 -
76 locale: 'fr_.*'
77 map:
78 janvier: 1
79 fevrier: 2
80 mars: 3
81 avril: 4
82 mai: 5
83 juin: 6
84 juillet: 7
85 aout: 8
86 septembre: 9
87 octobre: 10
88 novembre: 11
89 decembre: 12
90 page:
91 type: StaticRangeMapper
92 start: '1'
93 end: '99'
Posts by Author¶
1routeEnhancers:
2 AuthorPosts:
3 type: Extbase
4 extension: Blog
5 plugin: AuthorPosts
6 routes:
7 -
8 routePath: '/{author_title}'
9 _controller: 'Post::listPostsByAuthor'
10 _arguments:
11 author_title: author
12 -
13 routePath: '/{author_title}/page-{page}'
14 _controller: 'Post::listPostsByAuthor'
15 _arguments:
16 author_title: author
17 page: '@widget_0/currentPage'
18 defaultController: 'Post::listPostsByAuthor'
19 requirements:
20 author_title: '^[a-z0-9].*$'
21 page: '\d+'
22 aspects:
23 author_title:
24 type: PersistedAliasMapper
25 tableName: 'tx_blog_domain_model_author'
26 routeFieldName: 'slug'
27 page:
28 type: StaticRangeMapper
29 start: '1'
30 end: '99'
Category pages¶
1routeEnhancers:
2 BlogCategory:
3 type: Extbase
4 extension: Blog
5 plugin: Category
6 routes:
7 -
8 routePath: '/{category_title}'
9 _controller: 'Post::listPostsByCategory'
10 _arguments:
11 category_title: category
12 -
13 routePath: '/{category_title}/page-{page}'
14 _controller: 'Post::listPostsByCategory'
15 _arguments:
16 category_title: category
17 page: '@widget_0/currentPage'
18 defaultController: 'Post::listPostsByCategory'
19 requirements:
20 category_title: '^[a-z0-9].*$'
21 page: '\d+'
22 aspects:
23 category_title:
24 type: PersistedAliasMapper
25 tableName: sys_category
26 routeFieldName: 'slug'
27 page:
28 type: StaticRangeMapper
29 start: '1'
30 end: '99'
Blog Feeds¶
1routeEnhancers:
2 PageTypeSuffix:
3 type: PageType
4 map:
5 'blog.recent.xml': 200
6 'blog.category.xml': 210
7 'blog.tag.xml': 220
8 'blog.archive.xml': 230
9 'blog.comments.xml': 240
10 'blog.author.xml': 250
Blog Posts¶
1routeEnhancers:
2 BlogPosts:
3 type: Extbase
4 extension: Blog
5 plugin: Posts
6 routes:
7 -
8 routePath: '/page-{page}'
9 _controller: 'Post::listRecentPosts'
10 _arguments:
11 page: '@widget_0/currentPage'
12 defaultController: 'Post::listRecentPosts'
13 requirements:
14 page: '\d+'
15 aspects:
16 page:
17 type: StaticRangeMapper
18 start: '1'
19 end: '99'
Posts by Tag¶
1routeEnhancers:
2 BlogTag:
3 type: Extbase
4 extension: Blog
5 plugin: Tag
6 routes:
7 -
8 routePath: '/{tag_title}'
9 _controller: 'Post::listPostsByTag'
10 _arguments:
11 tag_title: tag
12 -
13 routePath: '/{tag_title}/page-{page}'
14 _controller: 'Post::listPostsByTag'
15 _arguments:
16 tag_title: tag
17 page: '@widget_0/currentPage'
18 defaultController: 'Post::listPostsByTag'
19 requirements:
20 tag_title: '^[a-z0-9].*$'
21 page: '\d+'
22 aspects:
23 tag_title:
24 type: PersistedAliasMapper
25 tableName: tx_blog_domain_model_tag
26 routeFieldName: 'slug'
27 page:
28 type: StaticRangeMapper
29 start: '1'
30 end: '99'
BlogStaticDatabaseMapper¶
1<?php
2 declare(strict_types = 1);
3
4 /*
5 * This file is part of the package t3g/blog.
6 *
7 * For the full copyright and license information, please read the
8 * LICENSE file that was distributed with this source code.
9 */
10
11 namespace T3G\AgencyPack\Blog\Routing\Aspect;
12
13 use TYPO3\CMS\Core\Database\ConnectionPool;
14 use TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface;
15 use TYPO3\CMS\Core\Utility\GeneralUtility;
16
17 class StaticDatabaseMapper implements StaticMappableAspectInterface, \Countable
18 {
19 /**
20 * @var array
21 */
22 protected $settings;
23
24 /**
25 * @var string
26 */
27 protected $field;
28
29 /**
30 * @var string
31 */
32 protected $table;
33
34 /**
35 * @var string
36 */
37 protected $groupBy;
38
39 /**
40 * @var array
41 */
42 protected $where;
43
44 /**
45 * @var array
46 */
47 protected $values;
48
49 /**
50 * @param array $settings
51 * @throws \InvalidArgumentException
52 */
53 public function __construct(array $settings)
54 {
55 $field = $settings['field'] ?? null;
56 $table = $settings['table'] ?? null;
57 $where = $settings['where'] ?? [];
58 $groupBy = $settings['groupBy'] ?? '';
59
60 if (!is_string($field)) {
61 throw new \InvalidArgumentException('field must be string', 1550156808);
62 }
63 if (!is_string($table)) {
64 throw new \InvalidArgumentException('table must be string', 1550156812);
65 }
66 if (!is_string($groupBy)) {
67 throw new \InvalidArgumentException('groupBy must be string', 1550158149);
68 }
69 if (!is_array($where)) {
70 throw new \InvalidArgumentException('where must be an array', 1550157442);
71 }
72
73 $this->settings = $settings;
74 $this->field = $field;
75 $this->table = $table;
76 $this->where = $where;
77 $this->groupBy = $groupBy;
78 $this->values = $this->buildValues();
79 }
80
81 /**
82 * {@inheritdoc}
83 */
84 public function count(): int
85 {
86 return count($this->values);
87 }
88
89 /**
90 * {@inheritdoc}
91 */
92 public function generate(string $value): ?string
93 {
94 return $this->respondWhenInValues($value);
95 }
96
97 /**
98 * {@inheritdoc}
99 */
100 public function resolve(string $value): ?string
101 {
102 return $this->respondWhenInValues($value);
103 }
104
105 /**
106 * @param string $value
107 * @return string|null
108 */
109 protected function respondWhenInValues(string $value): ?string
110 {
111 if (in_array($value, $this->values, true)) {
112 return $value;
113 }
114 return null;
115 }
116
117 /**
118 * Builds range based on given settings and ensures each item is string.
119 * The amount of items is limited to 1000 in order to avoid brute-force
120 * scenarios and the risk of cache-flooding.
121 *
122 * In case that is not enough, creating a custom and more specific mapper
123 * is encouraged. Using high values that are not distinct exposes the site
124 * to the risk of cache-flooding.
125 *
126 * @return string[]
127 * @throws \LengthException
128 */
129 protected function buildValues(): array
130 {
131 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
132 ->getQueryBuilderForTable($this->table);
133
134 $queryBuilder
135 ->select($this->field)
136 ->from($this->table);
137
138 if ($this->groupBy !== '') {
139 $queryBuilder->groupBy($this->groupBy);
140 }
141
142 if (!empty($this->where)) {
143 foreach ($this->where as $key => $value) {
144 $queryBuilder->andWhere($key, $queryBuilder->createNamedParameter($value));
145 }
146 }
147
148 return array_map('strval', array_column($queryBuilder->execute()->fetchAll(), $this->field));
149 }
150 }
Usage with imports¶
On typo3.com we are using imports to make routing configurations easier to manage:
1imports:
2 - { resource: "EXT:template/Configuration/Routes/Blog/BlogCategory.yaml" }
3 - { resource: "EXT:template/Configuration/Routes/Blog/BlogTag.yaml" }
4 - { resource: "EXT:template/Configuration/Routes/Blog/BlogArchive.yaml" }
5 - { resource: "EXT:template/Configuration/Routes/Blog/BlogAuthorPosts.yaml" }
6 - { resource: "EXT:template/Configuration/Routes/Blog/BlogFeedWidget.yaml" }
7 - { resource: "EXT:template/Configuration/Routes/Blog/BlogPosts.yaml" }
Full project example config¶
Taken from an anonymous live project:
1routeEnhancers:
2 news:
3 type: Extbase
4 extension: mynews
5 plugin: mynews
6 routes:
7 - routePath: '/news/detail/{news}'
8 _controller: 'News::show'
9 _arguments:
10 news: news
11 - routePath: '/search-result/{searchFormHash}'
12 _controller: 'News::list'
13 _arguments:
14 searchForm: searchForm
15 defaultController: 'News::show'
16 aspects:
17 news:
18 routeValuePrefix: ''
19 type: PersistedAliasMapper
20 tableName: 'tx_mynews_domain_model_news'
21 routeFieldName: slug
22 valueFieldName: uid
23 videos:
24 type: Extbase
25 extension: myvideos
26 plugin: myvideos
27 routes:
28 -
29 routePath: '/video-detail/detail/{videos}'
30 _controller: 'Videos::show'
31 _arguments:
32 videos: videos
33 -
34 routePath: '/search-result/{searchFormHash}'
35 _controller: 'Videos::list'
36 _arguments:
37 searchForm: searchForm
38 defaultController: 'Videos::show'
39 aspects:
40 videos:
41 routeValuePrefix: ''
42 type: PersistedAliasMapper
43 tableName: 'tx_myvideos_domain_model_videos'
44 routeFieldName: slug
45 valueFieldName: uid
46 discipline:
47 type: Extbase
48 extension: myvideos
49 plugin: overviewlist
50 routes:
51 -
52 routePath: '/video-uebersicht/disziplin/{discipline}'
53 _controller: 'Overview::discipline'
54 _arguments:
55 discipline: discipline
56 defaultController: 'Overview::discipline'
57 aspects:
58 discipline:
59 routeValuePrefix: ''
60 type: PersistedAliasMapper
61 tableName: 'tx_mytaxonomy_domain_model_discipline'
62 routeFieldName: slug
63 valueFieldName: uid
64 events:
65 type: Extbase
66 extension: myapidata
67 plugin: events
68 routes:
69 -
70 routePath: '/events/detail/{uid}'
71 _controller: 'Events::showByUid'
72 _arguments:
73 uid: uid
74 -
75 routePath: '/events/search-result/{searchFormHash}'
76 _controller: 'Events::list'
77 _arguments:
78 searchForm: searchForm
79 defaultController: 'Events::showByUid'
80 requirements:
81 uid: '[a-zA-Z0-9-_]+'
82 aspects:
83 uid:
84 routeValuePrefix: ''
85 type: PersistedAliasMapper
86 tableName: 'tx_myapidata_domain_model_event'
87 routeFieldName: slug
88 valueFieldName: uid
89 results:
90 type: Extbase
91 extension: myapidata
92 plugin: results
93 routes:
94 -
95 routePath: '/resultset/detail/{uid}'
96 _controller: 'Results::showByUid'
97 _arguments:
98 uid: uid
99 -
100 routePath: '/resultset/search-result/{searchFormHash}'
101 _controller: 'Results::list'
102 _arguments:
103 searchForm: searchForm
104 defaultController: 'Results::showByUid'
105 aspects:
106 uid:
107 routeValuePrefix: ''
108 type: PersistedAliasMapper
109 tableName: 'tx_myapidata_domain_model_event'
110 routeFieldName: slug
111 valueFieldName: uid
112 teams:
113 type: Extbase
114 extension: myapidata
115 plugin: teams
116 routes:
117 -
118 routePath: '/detail/{team}'
119 _controller: 'Team::show'
120 _arguments:
121 team: team
122 -
123 routePath: '/player/result/{searchFormHash}'
124 _controller: 'Team::list'
125 _arguments:
126 searchForm: searchForm
127 defaultController: 'Team::show'
128 requirements:
129 team: '[a-zA-Z0-9-_]+'
130 aspects:
131 team:
132 routeValuePrefix: ''
133 type: PersistedAliasMapper
134 tableName: 'tx_myapidata_domain_model_team'
135 routeFieldName: slug
136 valueFieldName: uid
137 moreLoads:
138 type: PageType
139 map:
140 'videos/events/videos.json': 1381404385
141 'videos/categories/videos.json': 1381404386
142 'videos/favorites/videos.json': 1381404389
143 'videos/newest/videos.json': 1381404390
EXT: DpnGlossary¶
Prerequisites:
The plugin for list view and detail view is added on one page.
The StaticMultiRangeMapper (a custom mapper) is available in the project.
Result:
List view:
https://example.org/<YOUR_PLUGINPAGE_SLUG>
Detail view:
https://example.org/<YOUR_PLUGINPAGE_SLUG>/term/the-term-title
1routeEnhancers:
2 DpnGlossary:
3 type: Extbase
4 limitToPages: [YOUR_PLUGINPAGE_UID]
5 extension: DpnGlossary
6 plugin: glossary
7 routes:
8 - { routePath: '/{character}', _controller: 'Term::list', _arguments: {'character': '@widget_0/character'} }
9 - { routePath: '/{localized_term}/{term_name}', _controller: 'Term::show', _arguments: {'term_name': 'term'} }
10 defaultController: 'Term::list'
11 defaults:
12 character: ''
13 aspects:
14 term_name:
15 type: PersistedAliasMapper
16 tableName: 'tx_dpnglossary_domain_model_term'
17 routeFieldName: 'url_segment'
18 character:
19 type: StaticMultiRangeMapper
20 ranges:
21 - start: 'A'
22 end: 'Z'
23 localized_term:
24 type: LocaleModifier
25 default: 'term'
26 localeMap:
27 - locale: 'de_DE.*'
28 value: 'begriff'
Taken from dpn_glossary manual.