The TYPO3 core code base receives major improvements with every released LTS version. There are always places that are refactored or streamlined and new things are introduced to supersede old and dusty concepts. After all, the common goal is to improve the code quality and provide better tools for developers. An excellent example for rather small changes that have a good impact are the various API classes that have been introduced with TYPO3 9 LTS:

  • The Environment API provides clean access to paths and the general environment
  • The Context API provides parameters relevant for data retrieval, such as access rights, current language, etc
  • The normalized Params contain information formerly retrieved by GeneralUtility::getIndpEnv()

In this Post we will look at each of these new API classes, what they offer and how we can use them.

Big shout-out to Benni Mack (@Twitter) who did most of the work!

Environment API

Let's start with the Environment API. The class \TYPO3\CMS\Core\Core\Environment was introduced (Patch, Feature RST) to supersede the use of PHP constants like PATH_site. The usage of constants was dropped in the entire core in favor of this new API (example Patch) and extension developers should do so as well. For now the class provides static functions to easily access environment related variables.

Time for an overview of what the class has to offer:

// Whether the current PHP request is handled by a CLI SAPI module or not.
// Supersedes TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI
Environment::isCli()

// Delivers the ApplicationContext object, usually defined in TYPO3_CONTEXT environment variables.
// This is something like "Production", "Testing", or "Development" or any additional information
// "Production/Staging".
// Supersedes GeneralUtility::getApplicationContext()
Environment::getContext()

// Informs whether TYPO3 has been installed via composer or not. Typically this is useful inside the
// Maintenance Modules, or the Extension Manager.
// Supersedes Bootstrap::usesComposerClassLoading()
Environment::isComposerMode()

// The root path to the project. For installations set up via composer, this is the path where your
// composer.json file is stored. For non-composer-setups, this is (due to legacy reasons) the public web folder
// where the TYPO3 installation has been unzipped (something like htdocs/ or public/ on your webfolder).
// However, non-composer-mode installations define an environment variable called "TYPO3_PATH_APP"
// to define a different folder (usually a parent folder) to allow TYPO3 to access and store data outside
// of the public web folder.
Environment::getProjectPath()

// The public web folder where index.php (= the frontend application) is put. This is equal to the legacy constant
// PATH_site, without the trailing slash. For non-composer installations, the project path = the public path.
// Supersedes PATH_site
Environment::getPublicPath()

// The folder where variable data like logs, sessions, locks, and cache files can be stored.
// When project path = public path, then this folder is usually typo3temp/var/, otherwise it's set to
// $project_path/var.
Environment::getVarPath()

// The folder where all global (= installation-wide) configuration like
//  - LocalConfiguration.php,
//  - AdditionalConfiguration.php, and
//  - PackageStates.php
//  is put.
//  This folder usually has to be writable for TYPO3 in order to work.
// 
//  When project path = public path, then this folder is usually typo3conf/, otherwise it's set to
//  $project_path/config.
Environment::getConfigPath()

// Previously known as PATH_typo3conf
// Please note that this might be gone at some point
// Supersedes PATH_thisScript
Environment::getCurrentScript()

// Whether this TYPO3 installation runs on windows
// Supersedes TYPO3_OS
Environment::isWindows()

// Whether this TYPO3 installation runs on unix (= non-windows machines)
// Supersedes TYPO3_OS
Environment::isUnix()

The superseded PHP constants are deprecated since TYPO3 9.4 (Deprecation RST) and should no longer be used as they will be removed in TYPO3 10. Better switch to the Environment API now!

To complete the overview of the Environment API let's have a look at the available additional helper methods. Note that these might become obsolete in the (near) future, so maybe don't rely on them in your code. However they are part of the LTS so we might look at them as well.

// Supersedes PATH_typo3conf . 'l10n/'
Environment::getLabelsPath()

// Supersedes PATH_typo3
Environment::getBackendPath()

// Supersedes PATH_typo3 . 'sysext/'
Environment::getFrameworkBasePath()

// Supersedes PATH_site . 'typo3conf/ext/'
Environment::getExtensionsPath()

// Supersedes PATH_typo3conf
Environment::getLegacyConfigPath()

As all of this methods are static, they can just be called from anywhere.

Noteworthy is the fact that with the introduction of the Environment API class TYPO3 is now capable of distinguishing between the project root (Environment::getProjectPath()) and the web accessible document root (Environment::getPublicPath()). The big advantage of this is that TYPO3 can now put part of the project related files outside the document root. With TYPO3 9 LTS this are the folders config (Environment::getConfigPath()) and var (Environment::getVarPath()). config contains all the site configurations, var contains logs, caches and labels and was previously part of typo3temp. Generally speaking these folders contain project related files that do not need to or should not be publicly available.

While composer based installations will have this set up automatically, non-composer based installations can  (and should) make use of this separation as well by setting the environment variable TYPO3_PATH_APP to the project root path. TYPO3 will pick this variable up and it will of course be respected in the Environment API class as well.

It's all quite nice and an obvious improvement already. But there is way more.

Let's move on to the Context API.

Top

Context API

The idea of the context API is to have one place that holds all relevant information for the restriction of data access. This includes language, time, session status, workspaces and so on. To give a quick example of such a set of restrictions: Imagine a page that is only visible to a logged in user that belongs to a certain group and only in the English version of the website. This means while retrieving the data TYPO3 has to take the language, session and user groups into account.

As we've already seen with the environment API the context API likewise streamlines and consolidates many parameters that used to be spread over various places in the core. The main object is the singleton instance of \TYPO3\CMS\Core\Context\Context introduced with TYPO3 9.4 (Patch, Feature RST). The class functions as a wrapper around the different aspects of the current context.

So let's find out what those are:

Top

Aspects

An aspect is a specific kind of context by which the data access could be restricted. For example in one request the login status might be relevant although another request might not care about that aspect but is concerned with e.g.. workspaces, while a third might take both into account.

The aspects, that are added by the core are the following:

  • DateTimeAspect ('date')
  • VisibilityAspect ('visibility')
  • WorkspaceAspect ('workspace')
  • UserAspect ('frontend.user')
  • UserAspect ('backend.user')
  • LanguageAspect ('language')

Each aspect has to implement the AspectInterface and has to be registered to the global context object with a unique identifier. The AspectInterface only asks for one method to be implemented:

interface AspectInterface
{
    /**
     * Get a property from an aspect
     *
     * @param string $name
     * @return mixed
     * @throws AspectPropertyNotFoundException
     */
    public function get(string $name);
}

This method is then used to directly retrieve aspect data from the Context. For example to access the current timestamp, we can use the DateTimeAspect with it's name 'date' by directly asking the Context for it:

$context = GeneralUtility::makeInstance(Context::class);
$currentTimestamp = $context->getPropertyFromAspect('date', 'timestamp');

What an Aspect has to offer relies on the implementation of get() as well as on additional public methods. In case the get() method is not sufficient the Context allows us to get a whole aspect object with $context->getAspect($name) as well.

Let's look at the aspects each at a time.

Top

DateTimeAspect

The DateTime aspect replaces $GLOBALS['SIM_EXEC_TIME'] and $GLOBALS['EXEC_TIME']. It offers some predefined formats of the current time as well as the full DateTime object:

// timestamp - unix timestamp number
$context->getPropertyFromAspect('date', 'timestamp');
// timezone - e.g. America/Los_Angeles
$context->getPropertyFromAspect('date', 'timezone');
// iso - datetime as string in ISO 8601 format, e.g. `2004-02-12T15:19:21+00:00`
$context->getPropertyFromAspect('date', 'iso');
// full - the DateTimeImmutable object
$context->getPropertyFromAspect('date', 'full');

If you use the before mentioned $GLOBALS anywhere in your code, switch to the DateTime aspect of the context API.

Top

WorkspaceAspect

The WorkspaceAspect holds information about the currently active workspace. It provides three data points:

// id - current workspace id
$context->getPropertyFromAspect('workspace', 'id');
// isLive - whether the current workspace is a live workspace
$context->getPropertyFromAspect('workspace', 'isLive');
// isOffline - whether the current workspace is a custom (offline) workspace
$context->getPropertyFromAspect('workspace', 'isOffline');

The WorkspaceAspect replaces $GLOBALS['BE_USER']->workspace in terms of information about the current workspace.

Top

VisibilityAspect

The VisibilityAspect holds information about the treatment of hidden records. Previously this information was available through $GLOBALS['TSFE']->showHiddenPages and $GLOBALS['TSFE']->showHiddenRecords. Both of these public properties of the TypoScriptFrontendController have been deprecated (Deprecation RST) and should no longer be consulted. Use the context API instead!

The aspect also provides the includeDeletedRecords flag, which is only used in the recycler system extension:

// includeHiddenPages - whether to include hidden=1 in pages tables
$context->getPropertyFromAspect('visibility', 'includeHiddenPages');
// includeHiddenContent - whether to include hidden=1 in tables except for pages
$context->getPropertyFromAspect('visibility', 'includeHiddenContent');
// includeDeletedRecords - whether to include deleted=1 records (only for use in recycler)
$context->getPropertyFromAspect('visibility', 'includeDeletedRecords');

If you want to see this aspect in action, have a look at the FrontendRestrictionContainer.

No time to lose, move on.

Top

UserAspect

Whether or not a user is logged in or belongs to a certain group is an often needed information. The two UserAspects bundle this information for the backend resp. frontend:

// id - uid of the user, 0 if no user is logged in
$context->getPropertyFromAspect('frontend.user', 'id');
$context->getPropertyFromAspect('backend.user', 'id');
// username - username of the user, '' if no user is logged in
$context->getPropertyFromAspect('frontend.user', 'username');
$context->getPropertyFromAspect('backend.user', 'username');
// isLoggedIn - whether a user is logged in
$context->getPropertyFromAspect('frontend.user', 'isLoggedIn');
$context->getPropertyFromAspect('backend.user', 'isLoggedIn');
// isAdmin - whether the current backend user is Admin, false for frontend.user
$context->getPropertyFromAspect('backend.user', 'isAdmin');
// groupIds - array of the groups the user is a member of
// frontend.user contains "-1" (hide at login) and "-2" (show at any login)
$context->getPropertyFromAspect('frontend.user', 'groupIds');
$context->getPropertyFromAspect('backend.user', 'groupIds');
// groupNames -array of names of all groups
$context->getPropertyFromAspect('frontend.user', 'groupNames');
$context->getPropertyFromAspect('backend.user', 'groupNames');

The class has one notable public method that is not available through a keyword. isUserOrGroupSet() returns true if a frontend user has its groups set to something else than '0,-1':

$isUserOrGroupSet = $context->getAspect('frontend.user')->isUserOrGroupSet();

This aspect replaces some public properties as well.

$GLOBALS['TSFE']->loginUser and $GLOBALS['TSFE']->beUserLogin are deprecated as well as $GLOBALS['TSFE']->gr_list (Deprecation RST).

Top

LanguageAspect

Last but not least another very important aspect was added to the context: the LanguageAspect (Patch). Information regarding the current page language are bundles in this aspect. As a result various language related properties of $GLOBALS['TSFE'] and the PageRepository have been deprecated as well (Deprecation RST). The aspect offers the following:

// id - the requested language of the current page (frontend), replaces $TSFE->sys_language_uid
$context->getPropertyFromAspect('language', 'id');
// contentId - the language of records to be fetched, replaces $TSFE->sys_language_content
$context->getPropertyFromAspect('language', 'contentId');
// fallbackChain - defined in config.sys_language_mode (strict/content_fallback:4,5,stop/ignore?)
// replaces $TSFE->sys_language_mode
$context->getPropertyFromAspect('language', 'fallbackChain');
// overlayType - defines which way the records should be fetched from ($TSFE->sys_language_contentOL and config.sys_language_overlay)
// - usually you fetch language 0 and -1, then take the "contentId" and "overlay" them
//  1. "on" if there is no overlay, do not render the default language records ("hideNonTranslated")
//  2. "mixed" - if there is no overlay, just keep the default language, possibility to have mixed languages - config.sys_language_overlay = 1
//  3. "off" - do not do overlay, only fetch records available in the current "contentId" (see above), and do not care about overlays or fallbacks - fallbacks could be an option here, actually that is placed on top
//  4. "includeFloating" - on + includeRecordsWithoutDefaultTranslation
$context->getPropertyFromAspect('language', 'overlayType');
// getLegacyOverlayType - same as above but returns 1 instead of 'mixed', 0 instead of 'off' and 'hideNonTranslated' instead of 'on' and 'includeFloating'
$context->getPropertyFromAspect('language', 'getLegacyOverlayType');
// legacyLanguageMode - Previously known as TSFE->sys_language_mode, here for compatibility reasons
$context->getPropertyFromAspect('language', 'legacyLanguageMode');

Your code should always consult the LanguageAspect instead of $GLOBALS['TSFE'] when information about the current language or the language configuration in general is needed.

Top

NormalizedParams

Finally I want to point to one more thing that qualifies as a new API and has to do with the general implementation of Middlewares (read my post PSR-15 Middlewares in TYPO3) and the PSR-7 request and response objects. In previous versions of TYPO3 the method GeneralUtility::getIndpEnv() was the API for accessing server parameters. The method was taking care of normalization of those parameters according to TYPO3 configuration.

All of this is now part of the Request object via an attribute called 'normalizedParams' (Patch, Feature RST). The attribute contains an instance of \TYPO3\CMS\Core\Http\NormalizedParams in which all server parameters have been stored in the same normalized form that getIndpEnv() would have returned them. This means that whenever the current Request object is available the NormalizedParams can be used as a 1-to-1 replacement for getIndpEnv().

The NormalizedParams object can be retrieved like this:

/** @var NormalizedParams $normalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');

I am not gonna list all replacements for calls to getIndpEnv(), because this complete lists exists in the before mentioned Feature RST. Just one quick example: Instead of calling getIndpEnv(TYPO3_REQUEST_HOST) we can now do $normalizedParams->getRequestHost().

But how can we access the current PSR-7 request object? In TYPO3 9 LTS this object is globally available in $GLOBALS['TYPO3_REQUEST']. Note that this is only a temporary solution that is very likely to change sooner or later. Follow the TYPO3 core development and read the release notes of the next TYPO3 releases to keep your code up to date in this regard.


With all this new stuff in place, extension developers should start switching to the new APIs as soon as they are involved with TYPO3 9LTS.

Only one thing left to say: Thanks for reading and supporting this blog!

Top

Further Information