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.