[BUGFIX] Fix example of StaticRangeMapper
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Documentation / Changelog / 9.5 / Feature-86365-RoutingEnhancersAndAspects.rst
1 .. include:: ../../Includes.txt
2 .. highlight:: yaml
3
4 ===============================================
5 Feature: #86365 - Routing Enhancers and Aspects
6 ===============================================
7
8 See :issue:`86365`
9
10 Description
11 ===========
12
13 Page-based routing is now flexible by adding enhancers to Routes that are generated or resolved with parameters, which
14 were previously appended as GET parameters.
15
16 An enhancer creates variants of a specific page-base route for a specific purpose (e.g. one plugin, one Extbase plugin)
17 and enhance the existing route path which can contain flexible values, so-called "placeholders".
18
19 On top, aspects can be registered to a specific enhancer to modify a specific placeholder, like static speaking names
20 within the route path, or dynamically generated.
21
22 To give you an overview of what the distinction is, we take a regular page which is available under
23
24 `https://www.example.com/path-to/my-page`
25
26 to access the Page with ID 13.
27
28 Enhancers are ways to extend this route with placeholders on top of this specific route to a page.
29
30 `https://www.example.com/path-to/my-page/products/{product-name}`
31
32 The suffix `/products/{product-name}` to the base route of the page is added by an enhancer. The placeholder variable
33 which is added by the curly braces can then be statically or dynamically resolved or built by an Aspect or more
34 commonly known a Mapper.
35
36 Enhancers and aspects are activated and configured in a site configuration, currently possible by modifying the
37 site's :file:`config.yml` and adding the :yaml:`routeEnhancers` section manually, as there is no UI available for
38 this configuration. See examples below.
39
40 It is possible to use the same enhancers multiple times with different configurations, however, be aware that
41 it is not possible to combine multiple variants / enhancers that match multiple configurations.
42
43 However, custom enhancers can be built to overcome special use cases where e.g. two plugins with multiple parameters
44 each could be configured. Otherwise, the first variant that matches the URL parameters is used for generation and
45 resolving.
46
47 Enhancers
48 ^^^^^^^^^
49
50 TYPO3 comes with the following enhancers out of the box:
51
52 - Simple Enhancer (enhancer type "Simple")
53 - Plugin Enhancer (enhancer type "Plugin")
54 - Extbase Plugin Enhancer (enhancer type "Extbase")
55
56 Custom enhancers can be registered by adding an entry to an extensions :file:`ext_localconf.php`.
57
58 :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['routing']['CustomPlugin'] = \MyVendor\MyPackage\Routing\CustomEnhancer::class;`
59
60 Within a configuration, an enhancer always evaluates the following properties:
61
62 * `type` - the short name of the enhancer as registered within :php:`$TYPO3_CONF_VARS`. This is mandatory.
63 * `limitToPages` - an array of page IDs where this enhancer should be called. This is optional. This property (array)
64   evaluates to only trigger an enhancer for specific pages. In case of special plugin pages it is
65   useful to only enhance pages with IDs, to speed up performance for building page routes of all other pages.
66
67 Simple Enhancer
68 ---------------
69
70 The Simple Enhancer works with various route arguments to map them to a argument to be used later-on.
71
72 `index.php?id=13&category=241&tag=Benni`
73 results in
74 `https://www.example.com/path-to/my-page/241/Benni`
75
76 The configuration looks like this::
77
78    routeEnhancers:
79      # Unique name for the enhancers, used internally for referencing
80      CategoryListing:
81        type: Simple
82        limitToPages: [13]
83        routePath: '/show-by-category/{category_id}/{tag}'
84        defaults:
85          tag: ''
86        requirements:
87          category_id: '[0-9]{1..3}'
88          tag: '^[a-zA-Z0-9].*$'
89        _arguments:
90          category_id: 'category'
91
92 The configuration option `routePath` defines the static keyword (previously known to some as "postVarSets" keyword for
93 some TYPO3 folks), and the available placeholders.
94
95 The `defaults` section defines which URL parameters are optional. If the parameters are omitted on generation, they
96 can receive a default value, and do not need a placeholder - it is also possible to add them at the very end of the
97 `routePath`.
98
99 The `requirements` section exactly specifies what kind of parameter should be added to that route as regular expression.
100 This way, it is configurable to only allow integer values for e.g. pagination. If the requirements are too loose, a
101 URL signature parameter ("cHash") is added to the end of the URL which cannot be removed.
102
103 The `_arguments` section defines what Route Parameters should be available to the system. In this example, the
104 placeholder is called `category_id` but the URL generation receives the argument `category`, so this is mapped to
105 this very name.
106
107 An enhancer is only there to replace a set of placeholders and fill in URL parameters or resolve them properly
108 later-on, but not to substitute the values with aliases, this can be achieved by Aspects.
109
110
111 Plugin Enhancer
112 ---------------
113
114 The Plugin Enhancer works with plugins on a page that are commonly known as `Pi-Based Plugins`, where previously
115 the following GET/POST variables were used:
116
117    `index.php?id=13&tx_felogin_pi1[forgot]=1&&tx_felogin_pi1[user]=82&tx_felogin_pi1[hash]=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345`
118
119 The base for the plugin enhancer is to configure a so-called "namespace", in this case `tx_felogin_pi1` - the plugin's
120 namespace.
121
122 The Plugin Enhancer explicitly sets exactly one additional variant for a specific use-case. In case of Frontend Login,
123 we would need to set up multiple configurations of Plugin Enhancer for forgot and recover passwords.
124
125 ::
126
127    routeEnhancers:
128      ForgotPassword:
129        type: Plugin
130        limitToPages: [13]
131        routePath: '/forgot-password/{user}/{hash}'
132        namespace: 'tx_felogin_pi1'
133        defaults:
134          forgot: "1"
135        requirements:
136          user: '[0-9]{1..3}'
137          hash: '^[a-zA-Z0-9]{32}$'
138
139 If a URL is generated with the given parameters to link to a page, the result will look like this:
140
141    `https://www.example.com/path-to/my-page/forgot-password/82/ABCDEFGHIJKLMNOPQRSTUVWXYZ012345`
142
143 If the input given to generate the URL does not meet the requirements, the route enhancer does not offer the
144 variant and the parameters are added to the URL as regular query parameters. If e.g. the user parameter would be more
145 than three characters, or non-numeric, this enhancer would not match anymore.
146
147 As you see, the Plugin Enhancer is used to specify placeholders and requirements, with a given namespace.
148
149 If you want to replace the user ID (in this example "82") with the username, you would need an aspect that can be
150 registered within any enhancer, but see below for details on Aspects.
151
152
153 Extbase Plugin Enhancer
154 -----------------------
155
156 When creating extbase plugins, it is very common to have multiple controller/action combinations. The Extbase Plugin
157 Enhancer is therefore an extension to the regular Plugin Enhancer, except for the functionality that multiple variants
158 are generated, typically built on the amount of controller/action pairs.
159
160 The `namespace` option is omitted, as this is built with `extension` and `plugin` name.
161
162 The Extbase Plugin enhancer with the configuration below would now apply to the following URLs:
163
164 * `index.php?id=13&tx_news_pi1[controller]=News&tx_news_pi1[action]=list`
165 * `index.php?id=13&tx_news_pi1[controller]=News&tx_news_pi1[action]=list&tx_news_pi1[page]=5`
166 * `index.php?id=13&tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]=13`
167 * `index.php?id=13&tx_news_pi1[controller]=News&tx_news_pi1[action]=archive&tx_news_pi1[year]=2018&&tx_news_pi1[month]=8`
168
169 And generate the following URLs
170
171 * `https://www.example.com/path-to/my-page/list/`
172 * `https://www.example.com/path-to/my-page/list/5`
173 * `https://www.example.com/path-to/my-page/detail/13`
174 * `https://www.example.com/path-to/my-page/archive/2018/8`
175
176 ::
177
178    routeEnhancers:
179      NewsPlugin:
180        type: Extbase
181        limitToPages: [13]
182        extension: News
183        plugin: Pi1
184        routes:
185          - { routePath: '/list/{page}', _controller: 'News::list', _arguments: {'page': '@widget_0/currentPage'} }
186          - { routePath: '/tag/{tag_name}', _controller: 'News::list', _arguments: {'tag_name': 'overwriteDemand/tags'}}
187          - { routePath: '/blog/{news_title}', _controller: 'News::detail', _arguments: {'news_title': 'news'} }
188          - { routePath: '/archive/{year}/{month}', _controller: 'News::archive' }
189        defaultController: 'News::list'
190        defaults:
191          page: '0'
192        requirements:
193          page: '\d+'
194
195 In this example, you also see that the `_arguments` parameter can be used to bring them into sub properties of an array,
196 which is typically the case within demand objects for filtering functionality.
197
198 Aspects
199 ^^^^^^^
200
201 Now that we've looked into ways on how to extend a route to a page with arguments, and to put them into the URL
202 path as segments, the detailed logic within one placeholder is in an aspect. The most common practice of an aspect
203 is a so-called mapper. Map `{news_title}` which is a UID within TYPO3 to the actual news title, which is a field
204 within the database table.
205
206 An aspect can be a way to modify, beautify or map an argument from the URL generation into a placeholder. That's why
207 the terms "Mapper" and "Modifier" will pop up, depending on the different cases.
208
209 Aspects are registered within one single enhancer configuration with the option `aspects` and can be used with any
210 enhancer.
211
212 Let's start with some simpler examples first:
213
214
215 StaticValueMapper
216 -----------------
217
218 The StaticValueMapper replaces values simply on a 1:1 mapping list of an argument into a speaking segment, useful
219 for a checkout process to define the steps into "cart", "shipping", "billing", "overview" and "finish", or in a
220 simpler example to create speaking segments for all available months.
221
222 The configuration could look like this:
223
224 ::
225
226    routeEnhancers:
227      NewsArchive:
228        type: Extbase
229        limitToPages: [13]
230        extension: News
231        plugin: Pi1
232        routes:
233          - { routePath: '/{year}/{month}', _controller: 'News::archive' }
234        defaultController: 'News::list'
235        defaults:
236          month: ''
237        aspects:
238          month:
239            type: StaticValueMapper
240            map:
241              january: 1
242              february: 2
243              march: 3
244              april: 4
245              may: 5
246              june: 6
247              july: 7
248              august: 8
249              september: 9
250              october: 10
251              november: 11
252              december: 12
253
254
255 You'll see the placeholder "month" where the aspect replaces the value to a speaking segment.
256
257 It is possible to add an optional `localeMap` to that aspect to use the locale of a value to use in multi-language
258 setups.
259
260 ::
261
262     routeEnhancers:
263       NewsArchive:
264         type: Extbase
265         limitToPages: [13]
266         extension: News
267         plugin: Pi1
268         routes:
269           - { routePath: '/{year}/{month}', _controller: 'News::archive' }
270         defaultController: 'News::list'
271         defaults:
272           month: ''
273         aspects:
274           month:
275             type: StaticValueMapper
276             map:
277               january: 1
278               february: 2
279               march: 3
280               april: 4
281               may: 5
282               june: 6
283               july: 7
284               august: 8
285               september: 9
286               october: 10
287               november: 11
288               december: 12
289           localeMap:
290             - locale: 'de_.*'
291               map:
292                 januar: 1
293                 februar: 2
294                 maerz: 3
295                 april: 4
296                 mai: 5
297                 juni: 6
298                 juli: 7
299                 august: 8
300                 september: 9
301                 oktober: 10
302                 november: 11
303                 dezember: 12
304
305
306 LocaleModifier
307 --------------
308
309 The enhanced part of a route path could be `/archive/{year}/{month}` - however, in multi-language setups, it should be
310 possible to rename `/archive/` depending on the language that is given for this page translation. This modifier is a
311 good example where a route path is modified but is not affected by arguments.
312
313 The configuration could look like this::
314
315    routeEnhancers:
316      NewsArchive:
317        type: Extbase
318        limitToPages: [13]
319        extension: News
320        plugin: Pi1
321        routes:
322          - { routePath: '/{localized_archive}/{year}/{month}', _controller: 'News::archive' }
323        defaultController: 'News::list'
324        aspects:
325          localized_archive:
326            type: LocaleModifier
327            default: 'archive'
328            localeMap:
329              - locale: 'fr_FR.*|fr_CA.*'
330                value: 'archives'
331              - locale: 'de_DE.*'
332                value: 'archiv'
333
334 You'll see the placeholder "localized_archive" where the aspect replaces the localized archive based on the locale of
335 the language of that page.
336
337
338 StaticRangeMapper
339 -----------------
340
341 A static range mapper allows to avoid the `cHash` and narrow down the available possibilities for a placeholder,
342 and to explicitly define a range for a value, which is recommended for all kinds of pagination functionalities.
343
344 ::
345
346    routeEnhancers:
347      NewsPlugin:
348        type: Extbase
349        limitToPages: [13]
350        extension: News
351        plugin: Pi1
352        routes:
353          - { routePath: '/list/{page}', _controller: 'News::list', _arguments: {'page': '@widget_0/currentPage'} }
354        defaultController: 'News::list'
355        defaults:
356          page: '0'
357        requirements:
358          page: '\d+'
359        aspects:
360          page:
361            type: StaticRangeMapper
362            start: '1'
363            end: '100'
364
365 This limits down the pagination to max. 100 pages, if a user calls the news list with page 101, then the route enhancer
366 does not match and would not apply the placeholder.
367
368 PersistedAliasMapper
369 --------------------
370
371 If an extension ships with a slug field, or a different field used for the speaking URL path, this database field
372 can be used to build the URL::
373
374     routeEnhancers:
375       NewsPlugin:
376         type: Extbase
377         limitToPages: [13]
378         extension: News
379         plugin: Pi1
380         routes:
381           - { routePath: '/detail/{news_title}', _controller: 'News::detail', _arguments: {'news_title': 'news'} }
382         defaultController: 'News::detail'
383         aspects:
384           news_title:
385             type: PersistedAliasMapper
386             tableName: 'tx_news_domain_model_news'
387             routeFieldName: 'path_segment'
388             routeValuePrefix: '/'
389
390 The PersistedAliasMapper looks up (via a so-called delegate pattern under the hood) to map the given value to a
391 a URL. The property `tableName` points to the database table, property `routeFieldName` is the field which will be
392 used within the route path for example.
393
394 The special `routeValuePrefix` is used for TCA type `slug` fields where the prefix `/` is within all fields of the
395 field names, which should be removed in the case above.
396
397 If a field is used for `routeFieldName` that is not prepared to be put into the route path, e.g. the news title field,
398 it still must be ensured that this is unique. On top, if there are special characters like spaces they will be
399 URL-encoded, to ensure a definitive value, a slug TCA field is recommended.
400
401 PersistedPatternMapper
402 ----------------------
403
404 When a placeholder should be fetched from multiple fields of the database, the PersistedPatternMapper is for you.
405 It allows to combine various fields into one variable, ensuring a unique value by e.g. adding the UID to the field
406 without having the need of adding a custom slug field to the system.
407
408 ::
409
410    routeEnhancers:
411      Blog:
412        type: Extbase
413        limitToPages: [13]
414        extension: BlogExample
415        plugin: Pi1
416        routes:
417          - { routePath: '/blog/{blogpost}', _controller: 'Blog::detail', _arguments: {'blogpost': 'post'} }
418        defaultController: 'Blog::detail'
419        aspects:
420          blogpost:
421            type: PersistedPatternMapper
422            tableName: 'tx_blogexample_domain_model_post'
423            routeFieldPattern: '^(?P<title>.+)-(?P<uid>\d+)$'
424            routeFieldResult: '{title}-{uid}'
425
426 The `routeFieldPattern` option builds the title and uid fields from the database, the `routeFieldResult` shows
427 how the placeholder will be output.
428
429 Impact
430 ======
431
432 Some notes to the implementation:
433
434 While accessing a page in TYPO3 in the Frontend, all arguments are currently built back into the global
435 GET parameters, but are also available as so-called `PageArguments` object, which is then used to be signed and verified
436 that they are valid, when handing them to process a frontend request further.
437
438 If there are dynamic parameters (= parameters which are not strictly limited), a verification GET parameter `cHash`
439 is added, which can and should not be removed from the URL. The concept of manually activating or deactivating
440 the generation of a `cHash` is not optional anymore, but strictly built-in to ensure proper URL handling. If you
441 really have the requirement to never have a cHash argument, ensure that all placeholders are having strict definitions
442 on what could be the result of the page segment (e.g. pagination), and feel free to build custom mappers.
443
444 Setting the TypoScript option `typolink.useCacheHash` is not necessary anymore when running with a site configuration.
445
446 Please note that Enhancers and Page-based routing is only available for pages that are built with a site configuration.
447
448 All existing APIs like `typolink` or functionality evaluate the new Page Routing API directly and come with route
449 enhancers.
450
451 Please note that if you update the Site configuration with enhancers that you need to clear all caches.
452
453 .. index:: Frontend, PHP-API