Neos/Flow Update to 6.x - A Guide

Here's how the PHP standards PSR-3 and PSR-7 are applied to the client project

if (sad() === true) { sad().stop() ; beAwesome(); }

André Hoffmann
Ob Frontend, ob Backend, ob Javascript, ob PHP er findet immer einen Weg zu ihrem Erfolg.
Reading duration: approx. 7 Minutes

The Starting Point

As an employee of punkt.de, I work on many projects and use a wide variety of technologies to meet our clients’ needs. One recent project involved updating a Neos/Flow application. In this client project, Flow Neos is used, among other things, for centralized data storage and ensures that data is displayed only where it is authorized to be displayed (on the Internet or intranet). Since this happens reliably and unnoticed in the background, the updates were somewhat neglected, and as a result, the project fell several major releases behind.

The goal is 5.x

Since we needed to catch up on two major versions, the update should be carried out in two steps. First, we’ll update the application from Neos/Flow 4.3 to Neos/Flow 5.0 or 5.3 to keep things simple.

A `composer update` rarely comes alone

The first step begins with editing the `composer.json` file. Quickly set the version to 5.0.0, run a `composer update` in the terminal within the project, and you’ll immediately see the first error messages. So take a look and see what Composer is complaining about. In most cases, there are likely to be issues with the versions used in the project. Especially in a project that hasn’t been updated in a long time, you’re bound to run into pinned, outdated versions of your dependencies. Even if you rarely perform such updates, Composer helps by providing suggestions for possible dependency adjustments. This makes it easy to make progress even with these larger updates.
For example, it turned out that our project was still using an old version of our package for the error monitoring solution Sentry. The project was using version 1.2.0. A look at the repository’s releases page on GitHub showed that this package, too, already has two newer major versions. After looking at the package’s `composer.json` file, you can quickly see that you can use the latest version. The steps that follow are straightforward, so you can also quickly find the appropriate packages for automated tests—such as Behat or PHPUnit —and customize them where necessary. It makes sense to remove the test packages for now to reduce complexity and focus on the essentials. For the tests, I then added the dependencies later using `composer require --dev package-name` to install the latest version.

./flow makes (developer) life easier

Once `composer update` has successfully updated both Neos/Flow and all other dependencies without errors, you can run `./flow` in the project directory. The error messages that follow are explained on the Neos update page. At this point, it’s important to understand the relationship between the Neos CMS and the Neos/Flow framework.

Neos/Flow is an MVC framework that can be used to develop any type of web application. It offers all the features of common MVC frameworks, such as dependency injection and database abstraction using Doctrine. Furthermore, Neos/Flow serves as the foundation for the Neos CMS, similar to how ExtBase serves as the foundation for TYPO3. Since the Neos version is always one lower than the Neos/Flow version being used, I found the information relevant to my update from Neos/Flow 4.3 to 5.0 in the article covering the update from Neos 3.3 to 4.0. Under the “Breaking Changes” section, I learned that the Symfony YAML component used in this version was stricter when parsing YAML configuration files. This was evident in the error messages I saw when running the command ./flow in the terminal. After adjusting the configuration according to the instructions in the error messages, I was able to run the ./flow command and—following the instructions on the update page—complete the database adjustments and code migrations via the terminal.

Finally standards-compliant—PSR-3 LoggerInterface

Since the goal was generally to upgrade to Neos/Flow 5.3, I didn’t hesitate and used Composer to update to that version. Here, I also re-added the development dependencies for the various tests in their latest versions to the project in order to have a stable intermediate state of the project with Neos/Flow 5.3.

A major change introduced with Neos/Flow 5.0 is the adoption of the PHP standard PSR-3. This standard defines a LoggerInterface that is new to Neos/Flow. Implementing this standard now requires extensive code adjustments within the project. In Neos/Flow versions <= 4.3, Neos/Flow’s own LoggerInterface defined the following two functions for logging:

public function log( $message, $severity = LOG_INFO, $additionalData = null, $packageKey = null, $className = null, $methodName = null );

public function logException(\Exception $exception, array $additionalData = []);

According to the PSR-3 definition, the `dielog` function is now supplemented by additional functions such as `info`, `warning`, and `debug`, and these should be used preferentially. Furthermore, the `logException` method has been removed from the general Neos/Flow `LoggerInterface` and replaced by the `logThrowable` method definition in the `ThrowableStorageInterface`. Based on this information, I adapted the code and replaced calls such as

$this->logger->log('my message', LOG_INFO);

through

$this->logger->info('my message', LogEnvironment::fromMethodName(__METHOD__));

The LogEnvironment::fromMethodName(__METHOD__) function returns the context information that has been automatically added in the background by the framework up to this point. If the LogEnvironment's context information is insufficient, it can be supplemented with additional information, for example, as follows:

$this->logger->info( 'my message', array_merge(LogEnvironment::fromMethodName(__METHOD__), ['extraInformation' => $data]) );

One minor stumbling block for me was the difference in behavior between the `logException` and `logThrowable` functions. Unlike with `logException`, an exception is not automatically logged. This means you can't simply replace `logException` with `logThrowable`. Instead, you have to handle logging the exception yourself:

$this->logger->error( 'Error occurred, exception follows:'. $this->throwableStorage->logThrowable($exception), LogEnvironment::fromMethodName(__METHOD__) );

In addition to these changes, the `use` statements and type hints naturally had to be updated as well:

use Psr\Log\LoggerInterface; ... /** * @var LoggerInterface * @Flow\Inject */ protected $logger;

If necessary, the configuration may need to be adjusted if custom loggers are used in the project. For me, the best documentation at this point was the Neos/Flow code itself, since this is a very specific case and therefore not really documented in great detail.

The final step was to stabilize the application using the various tests, which, in my case, covered the application’s functionality very well. Kudos to my colleagues for that. This made my work—and the search for potential bugs—a lot easier.

After passing the unit, functional, and Behat acceptance tests, I could be fairly certain that the application was working just as it had before and that I had successfully completed the update to Neos/Flow 5.3.

The final version is Neos/Flow 6.x

Once the 5.3 intermediate step is complete, you can update the application to the latest version (Neos/Flow 6.x).

Déjà vu, or: Run `composer update` and `./flow` starts over from the beginning

I upgraded Neos/Flow to version 6.0, checked the error messages from `composer update`, made sure all dependencies were met, and verified that the application could be installed via Composer without errors. The errors flagged by `./flow` were also quickly identified and fixed thanks to the update page.
After adjusting the logger configuration in the project, I had already made significant progress.

Even More Standard Compliance—PSR-7 HTTP Message Interface

With Neos/Flow 6.0, the PHP standard PSR-7 made its way into the world of Neos/Flow and the Neos CMS. This standard defines the two interfaces `Psr\Http\Message\RequestInterface` and `Psr\Http\Message\ResponseInterface`, thereby specifying how communication between a server and a client should be represented.

Furthermore, it was specified that the body of a message is represented by the `Psr\Http\Message\StreamInterface`. By using streams, you can access various types of message content—such as text, images, or videos—through a unified API.

Since, in my case, the Neos/Flow application functions as a web API for a TYPO3 instance, I set out to make the application PSR-7-compliant. Neos/Flow leverages the benefits provided by PSR-7 and uses the PSR-7-compatible implementations from the guzzlehttp package to represent requests to the server. In addition, Neos/Flow uses the ActionRequest and ActionResponse classes for use in an MVC context. Within Neos/Flow, these serve as an additional abstraction layer on top of the PSR-7 implementations. Armed with this knowledge, I set out to adapt the project code.

The following examples show how, for instance, a file transfer from a server to a client was previously implemented and what a PSR-7-compliant implementation might look like. This code should be placed in an ActionController, where direct access to the ActionRequest is possible via the controller’s $request property.

PHP-native functions

header('Content-type: ' . $contentType); header('Content-Disposition: attachment; filename="' . basename($filePath)); 
header("Cache-Control: max-age=0,private,must-revalidate"); 
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); 
ob_clean(); 
readfile($filePath); 
flush();
exit();

PSR-7 Using Streams

$this->response->setContentType($contentType); 
$this->response->setComponentParameter( SetHeaderComponent::class,'Content-Disposition', 'attachment; filename="' . basename($filePath) ); 
$this->response->setComponentParameter(SetHeaderComponent::class,'Cache-Control', 'max-age=0,private,must-revalidate'); 
$this->response->setComponentParameter(SetHeaderComponent::class,'Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); 
return GuzzleHttp\Psr7\stream_for(fopen($filePath, 'r+'));

Neos/Flow >= 6.0

$this->response->setHeader('Content-type', 'application/pdf'); 
$this->response->setHeader('Content-Disposition', 'attachment; filename="' . $fileName); 
$this->response->setContent($content); 
$this->response->send();

PSR-7 Using Streams

$this->response->setContentType('application/pdf'); 
$this->response->setComponentParameter( SetHeaderComponent::class,'Content-Disposition', 'attachment; filename="' . $fileName);
return stream_for($content);

In addition to these changes, which are directly related to the PSR-7 standard, there were also indirect changes, as some functions—such as `sendHeaders` and `send`—were removed from the previous `ActionResponse` implementation. The following examples show how to handle this:

File download from Server Neos/Flow < 6.0

$this->response->setHeader('Content-type', 'application/pdf'); 
$this->response->setHeader('Content-Disposition', 'attachment; filename="' . basename($this->absoluteFilePath)); 
$this->response->sendHeaders(); 
ob_clean(); 
readfile($this->absoluteFilePath); 
flush();

Neos/Flow => 6.0

$this->response->setContentType('application/pdf'); 
$this->response->setComponentParameter( SetHeaderComponent::class, 'Content-Disposition', 'attachment; filename="' . basename($absoluteFilePath) );
return stream_for(fopen($absoluteFilePath, 'rb'));

Terminating request processing and sending an error message when an error occurs in Neos/Flow < 6.0

$this->response->setStatus($httpStatusCode); 
$this->response->setContent( json_encode( [ 'statusCode' => $internalStatusCode, 'statusMessage' => $internalStatusMessage ] ) ); 
$this->response->send(); exit();

Neos/Flow => 6.0

$this->throwStatus( $httpStatusCode, $internalStatusMessage, json_encode( [ 'statusCode' => $internalStatusCode, 'statusMessage' => $internalStatusMessage ] ) );

Alongside these adjustments, I performed code refactoring and bug fixes—whenever the opportunity arose—to stabilize this update version as well.

I followed the same approach as with the 5.3 milestone and made extensive use of the various tests available to me to detect and fix potential bugs. After all tests passed without errors, I was able to update the application to the latest Neos/Flow version 6.1 with minimal effort.

But the update wasn’t quite finished yet. In a final step, the application was checked and linted using the PHP linter PHP_CodeSniffer to update the code style as well.

Conclusion

Updating across two major versions is a major undertaking, but it’s worth the effort because the new version supports the PRS-7 standard, making it easier for all team members to collaborate on a large project.

Since Flow plays a central role in the project, I was able to get a good overview of the entire project without having to delve too deeply into the details. After a few exciting days, the update is complete without any major issues and is now ready for use in the project.

Share:

More articles

You can’t be afraid to fail. It’s the only way you succeed.
Eren Ceviz, Entwicklung at punkt.de
Working at punkt.de