Services in TypoScript

Symfony has an excellent service container with dependency injection. But in a while you have to configure some user function in TypoScript or some Hooks, which are expecting the class name. This would prevent the use of proper DI.

Fortunately Bartacus integrates the service container into TYPO3 so you can access a service in a TypoScripts userFunc or hooks.

Caution

To get the user functions in TypoScript working Bartacus XCLASSes the ContentObjectRender in a very early phase. If you have an extension installed which wants to XCLASS the the same class, the extension wins, and this functionality stops working.

TypoScript userFunc

Define your class as service with the tag typo3.user_func. This will expose all public function to be accessible in TypoScript. For more information about the service container see the Symfony Service Container Documentation.

  • YAML
    services:
        helper.frontend:
            class: Acme\Extensions\Content\Helper\FrontendHelper
            lazy: true
            tags:
                -  { name: typo3.user_func }
    
  • XML
    <services>
        <service id="helper.frontend" class="Acme\Extensions\Content\Helper\FrontendHelper" lazy="true">
            <tag name="typo3.user_func"/>
        </service>
    </services>
    
  • PHP
    use Symfony\Component\DependencyInjection\Definition;
    
    $definition = new Definition('Acme\Extensions\Content\Helper\FrontendHelper');
    $definition->setLazy(true);
    $definition->addTag('typo3.user_func');
    $container->setDefinition('helper.frontend', $definition);
    

Note

The service example above is marked as a lazy service. These is a MUST to have a correct instance injected. Otherwise your service is created too early and you have a wrong dependencies injected.

Now you can use your service in a TypoScript userFunc and consorts:

site.config.titleTagFunction = helper.frontend->getPageTitle

site.10 = TEMPLATE
site.10 {
    template = FILE
    template.file = fileadmin/mastertemplate.html
    marks {

        LOGO = USER
        LOGO.userFunc = helper.frontend->getLogo

        COPYRIGHT= USER
        COPYRIGHT.userFunc = helper.frontend->getCopyright

        FOOTERMENU < footerMenu
        MAINMENU < mainMenu
        METAMENU < metaMenu

        SUBTEMPLATE = TEMPLATE
        SUBTEMPLATE {
            template = FILE
            template.file.preUserFunc = helper.backend_layout->getLayout
            marks {
                CONTENT0 < styles.content.get
                CONTENT1 < styles.content.get
                CONTENT1.select.where = colPos=1
            }
        }
    }
}

Normally you would get passed the calling ContentObjectRender passed into a public property cObj. When using services for user functions you get passed the calling content object as third parameter to the method.

Bonus: Hooks

The way the user functions are made accessible is also available for hooks, which use callUserFunction().

// ext_localconf.php

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'][] = 'hook.news->clearCachePostProc';

If the hook uses getUserObj() instead, you must add the typo.user_obj tag to your service.

// ext_localconf.php

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typolinkLinkHandler']['tel'] = 'hook.link';

Note

In future iterations Bartacus will abstract the way of defining hooks. Either with another service tag or through the Symfony event dispatcher.

If there are services which expects user objects, but are special in case of the using syntax like custom TCA eval functions, you can add an alias to the tag e.g. <tag name="typo3.user_obj" alias="my_alias"/> and the resulting string for using in user obj is my_service:&my_alias.