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->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    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 manual.