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: 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 the routing examples in the "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 aspects:
33 year:
34 type: BlogStaticDatabaseMapper
35 table: 'pages'
36 field: 'crdate_year'
37 groupBy: 'crdate_year'
38 where:
39 doktype: 137
40 month:
41 type: StaticValueMapper
42 map:
43 january: 1
44 february: 2
45 march: 3
46 april: 4
47 may: 5
48 june: 6
49 july: 7
50 august: 8
51 september: 9
52 october: 10
53 november: 11
54 december: 12
55 localeMap:
56 -
57 locale: 'de_.*'
58 map:
59 januar: 1
60 februar: 2
61 maerz: 3
62 april: 4
63 mai: 5
64 juni: 6
65 juli: 7
66 august: 8
67 september: 9
68 oktober: 10
69 november: 11
70 dezember: 12
71 -
72 locale: 'fr_.*'
73 map:
74 janvier: 1
75 fevrier: 2
76 mars: 3
77 avril: 4
78 mai: 5
79 juin: 6
80 juillet: 7
81 aout: 8
82 septembre: 9
83 octobre: 10
84 novembre: 11
85 decembre: 12
86 page:
87 type: StaticRangeMapper
88 start: '1'
89 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 aspects:
20 author_title:
21 type: PersistedAliasMapper
22 tableName: 'tx_blog_domain_model_author'
23 routeFieldName: 'slug'
24 page:
25 type: StaticRangeMapper
26 start: '1'
27 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 aspects:
20 category_title:
21 type: PersistedAliasMapper
22 tableName: sys_category
23 routeFieldName: 'slug'
24 page:
25 type: StaticRangeMapper
26 start: '1'
27 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 aspects:
14 page:
15 type: StaticRangeMapper
16 start: '1'
17 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 aspects:
20 tag_title:
21 type: PersistedAliasMapper
22 tableName: tx_blog_domain_model_tag
23 routeFieldName: 'slug'
24 page:
25 type: StaticRangeMapper
26 start: '1'
27 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->executeQuery()->fetchAllAssociative(), $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 aspects:
81 uid:
82 routeValuePrefix: ''
83 type: PersistedAliasMapper
84 tableName: 'tx_myapidata_domain_model_event'
85 routeFieldName: slug
86 valueFieldName: uid
87 results:
88 type: Extbase
89 extension: myapidata
90 plugin: results
91 routes:
92 -
93 routePath: '/resultset/detail/{uid}'
94 _controller: 'Results::showByUid'
95 _arguments:
96 uid: uid
97 -
98 routePath: '/resultset/search-result/{searchFormHash}'
99 _controller: 'Results::list'
100 _arguments:
101 searchForm: searchForm
102 defaultController: 'Results::showByUid'
103 aspects:
104 uid:
105 routeValuePrefix: ''
106 type: PersistedAliasMapper
107 tableName: 'tx_myapidata_domain_model_event'
108 routeFieldName: slug
109 valueFieldName: uid
110 teams:
111 type: Extbase
112 extension: myapidata
113 plugin: teams
114 routes:
115 -
116 routePath: '/detail/{team}'
117 _controller: 'Team::show'
118 _arguments:
119 team: team
120 -
121 routePath: '/player/result/{searchFormHash}'
122 _controller: 'Team::list'
123 _arguments:
124 searchForm: searchForm
125 defaultController: 'Team::show'
126 aspects:
127 team:
128 routeValuePrefix: ''
129 type: PersistedAliasMapper
130 tableName: 'tx_myapidata_domain_model_team'
131 routeFieldName: slug
132 valueFieldName: uid
133 moreLoads:
134 type: PageType
135 map:
136 'videos/events/videos.json': 1381404385
137 'videos/categories/videos.json': 1381404386
138 'videos/favorites/videos.json': 1381404389
139 '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 extension manual.