TYPO3 was known for a long time to prefer homemade solutions over industry standards. This is about to change for good (where it makes sense, I may add). A widely noticed step was the switch from the custom TYPO3 coding guidelines to PSR-2 for the 7 LTS release (see the patch on gerrit).

But this was just the beginning. PSR-7 has been implemented since May 2015 (patch) and more is to come. In this post we have a quick (and not too deep) look into some steps towards standards or just new and fresh technologies in TYPO3. Developers will be more than happy to try and play around with the TYPO3 master branch in which all of the following is already merged.

PHP 7

As announced on March the 3rd TYPO3 8 runs on PHP 7 only. I quote:

With the release of TYPO3 8 LTS in April 2017 we can expect every major distribution to provide PHP 7 in their default stable builds.

[...]

PHP7 comes with significant performance improvements, users will love the more fluent system when working with TYPO3. We do not want to cripple this by supporting an old PHP version that is roughly half as quick. Hosters will love TYPO3 8 with PHP7 for the reduced system load, too. Last but not least, this is perfectly in line with green IT efforts.

We can look forward to use all the PHP 7 features in TYPO3. In fact newly written code is encouraged to be strictly typed, as this (shortened) example from the recently introduced \TYPO3\CMS\Core\Database\ConnectionPool shows:

<?php
declare (strict_types = 1);
namespace TYPO3\CMS\Core\Database;

class ConnectionPool
{
    /**
     * ...
     */
    public function getConnectionForTable(string $tableName): Connection
    {
        if (empty($tableName)) {
            throw new \UnexpectedValueException(
                'ConnectionPool->getConnectionForTable() requires a table name to be provided.',
                1459421719
            );
        }

        $connectionName = ConnectionPool::DEFAULT_CONNECTION_NAME;
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])) {
            $connectionName = (string)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
        }

        return $this->getConnectionByName($connectionName);
    }

Let's mention some good practices from the above code: The whole class enforces strict typing with the declare(strict_types = 1) in line 2. Alongside the now possible scalar type declaration string $tableName in line 10 this will cause a fatal error if a non-string is passed to this method from within class with activated strict typing.

Also on line 10 we see the return type of the method appended with : Connection.

For a deeper understanding and a nice roundtrip regarding PHP 7 paradigms and its place in the world of programming languages, I recommend the talk "PHP 7: What kind of Langauge is it?" by Sebastian Bergmann, wich I attend at the PHP Conference 2016 in Berlin. Apparently the talk is on youtube.

So if you have a spare hour, enjoy.

Btw. the above mentioned class was written during the introduction of Doctrine DBAL to TYPO3.

Wait, what?

Top

Doctrine DBAL

The TYPO3 Database Abstraction Layer (DBAL) was tightly bound to the DatabaseConnection class that one could reliably find in $GLOBALS['TYPO3_DB']. Don't worry - it is still there :)

BUT and thats the big news: the Doctrine DBAL library is also there. TYPO3 is officially supporting it and has moved all database calls from $GLOBALS['TYPO3_DB'] to the doctrine syntax. Have a look at an example patch if you are interested on how to migrate your own code or read the blog post "Migrate from the TYPO3 database wrapper to the Doctrine DBAL syntax" by Jan Helke.

Here is how a SELECT query looks like the doctrine way. Let's find all backend users that don't have the uid 1 and whose usernames don't start with "_cli_":

/** @var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
$users = $queryBuilder
  ->select('*')
  ->from('be_users')
  ->where($queryBuilder->expr()->neq('uid', 1))
  ->andWhere($queryBuilder->expr()->notLike('username', $queryBuilder->createNamedParameter('_cli_%')))
  ->orderBy('username')
  ->execute()
  ->fetchAll();

Since it is now possible to have multiple databases behind your TYPO3 you get the Connection on a per table base with getQueryBuilderForTable($tableName).

Morton Jonuschat, whom we have to thank the most for this big achievement, also provided his slides on the topic on speakerdeck (check out Moving TYPO3 CMS to Doctrine DBAL on speakerdeck).

Enjoy!

Top

Standalone Fluid

The nameless coder with the name of Claus Due started an ambitious project when he descided to decouple the template engine fluid from any framework and create a standalone version that can be friends with every PHP project. Have a look at it on github. It's real!

But on top of that he implemented tons of features. Wanna go math in Fluid like {myNumber + 1}? Check! Wanna go ternary: {myToggleVariable ? myThenVariable : myElseVariable}? Check! Wanna do this: <f:if condition="({variableOne} && {variableTwo}) || {variableThree} || {variableFour}">? Check! Dynamically pick an item from an array like this {data.{key}}? Check!

There are elseif options, new ViewHelpers, support for data attributes on all tag-ViewHelpers and tons and tons more. And of course Fluid gained a huge performance boost as well. You can get a better idea of all the shiny things this baby can do by reading the Feature RST file that came with the patch that exchanged most parts of the sysext fluid with the standalone version as a composer dependency for the TYPO3 core.

Thank you, Claus!

Top

Codeception Acceptance Tests

Another big improvement in the area of test coverage was the (re)introduction of Codeception for the sake of introducing acceptance tests. As described in the wiki, the tests can be executed in a local environment if PhantomJS is installed.

Let's have a look at a simple test for the login of a non admin user:

namespace TYPO3\CMS\Core\Tests\Acceptance\Backend\Login;

use TYPO3\CMS\Core\Tests\Acceptance\Step\Backend\Kasper;
use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\Topbar;

class EditorCest
{
    /**
     * Login as a non-admin user, check visible modules and logout again
     *
     * @param Kasper $I
     */
    public function loginAsEditor(Kasper $I)
    {
        $I->loginAsEditor();
        // user is redirected to 'about modules' after login, but must not see the 'admin tools' section
        $I->cantSee('Admin tools', '#typo3-menu');
        $topBarItemSelector = Topbar::$containerSelector . ' ' . Topbar::$dropdownToggleSelector . ' *';
        // can see bookmarks
        $I->seeElement($topBarItemSelector, ['title' => 'Bookmarks']);
        // cant see clear cache
        $I->cantSeeElement($topBarItemSelector, ['title' => 'Clear cache']);
        $I->logout();
        $I->waitForElement('#t3-username');
    }
}

The tests are executed after each merge and grow in number each week. On a sidenode: the class Kasper is a reference to Kasper Skårhøj, who invented TYPO3 back in the days.

And we are not done yet!

Top

Symfony Console

The famous Symfony Console Component has also been integrated into TYPO3 to improve the capabilities in terms of CLI requests. As an alternative to the typo3/cli_dispatch.phpsh a new command line tool was introduced with typo3/sysext/core/bin/typo3 that is linked to bin/typo3 when TYPO3 is installed with composer.

Extensions can register commands in Configuration/Commands.php like this:

return [
    'mystuff:mytask' => [
        'class' => \Vendor\MyExt\Command\MyStuffCommand::class,
        'user' => '_cli_lowlevel'
    ]
];

The specified class is then callable with bin/typo3 mystuff:mytask. An example of such a command class could look like this:

<?php
namespace Vendor\MyExt\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class MyStuffCommand extends Command
{

    /**
     * Configure the command by defining the name, options and arguments
     */
    public function configure()
    {
        $this->addOption(
               'dry',
               null,
               InputOption::VALUE_NONE,
               'If set, the task will run in dry mode'
         );
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->doActualStuff((bool)$input->getOption('dry'));
    }

You can run this command in dry mode with bin/typo3 mystuff:mytask --dry. Btw: the list command always tells you what commands are registered:

./bin/typo3 list
TYPO3 CMS version 8.1.0-dev

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  help                   Displays help for a command
  list                   Lists commands
 backend
  backend:lock           Lock the TYPO3 Backend
  backend:unlock         Unlock the TYPO3 Backend
 referenceindex
  referenceindex:update  Update the reference index of TYPO3

and we are still not done ...

Top

Guzzle

Another standard was introduced with the well known HTTP request library Guzzle. It has already been integrated in the TYPO3 core (read the Feature RST file) and can be used by extension developers to perform PSR-7 compliant requests.

To quote from the Guzzle documentation,

Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.

To make use of the Guzzle client we instanciante the class \TYPO3\CMS\Core\Http\RequestFactory and call the request() method:

$requestFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\RequestFactory\RequestFactory::class);
$additionalOptions = [
   'headers' => ['Cache-Control' => 'no-cache'],
];
$response = $requestFactory->request('https://usetypo3.com', 'GET', $additionalOptions);
if ($response->getStatusCode() === 200) {
   if ($response->getHeader('Content-Type') === 'text/html') {
      $content = $response->getBody()->getContents();
   }
}

When instanciated by the RequestFactory the Guzzle client is always preconfigured with the content of $GLOBALS['TYPO3_CONF_VARS']['HTTP']. All possible request options supported by Guzzle can be set here.

Top

While all these great things found their way into TYPO3 it is hard to believe that not even half a year lies between the celebrated release of TYPO3 7 LTS and the (not so much celebrated) release of this article. So, as usual thanks to the core team and to all contributers for this impressive work.

As a developer and a contributer, I am really happy.