Neos 9 Beta-14 Release

It’s time to announce our latest Neos 9 Beta.

It contains many bugfixes, big refactorings and great new features.

Thanks to all who contributed <3.

Upgrade instructions from Beta 11 / Beta 13

previous beta → Neos 9 Beta-11 Release

In your composer.json you should require 9.0.0-beta14 for all Neos packages from the development distribution and for the NeosUi as well as Flow:

"neos/flow": "9.0.0-beta14",
"neos/neos": "9.0.0-beta14",
"neos/neos-ui": "9.0.0-beta14"

The database can be migrated the following way:

A setup to apply the latest schema adjustments can be run via (feel free to inspect via cr:status what will be adjusted)

./flow doctrine:migrate
./flow cr:setup

Additionally, PRs marked with :boom:⛁ might need a dedicated migration.

  • :boom:⛁¹ Migration for creating missing Neos.Neos metadata and role assignments for the workspace
./flow migrateevents:migrateWorkspaceMetadataToWorkspaceService

Features

!!! 1.) :sparkles: Highlight :sparkles: Workspaces with metadata and role assignment

Introduction of Neos.Neos workspace metadata and user-assignment

A new WorkspaceService was introduced as central authority to manage Neos workspaces (Neos\Neos\Domain\Service\WorkspaceService).
With new features and possibilities that could not be part of the content repository core, setting the first step for security and policies in Neos 9.
The new service handles metadata like workspace title, description as well as owner assignments and new assignments of individual users to a workspace or also flow roles.
Following new commands were introduced to work with workspaces:

  • ./flow workspace:createroot: Create a new root workspace for a content repository
  • ./flow workspace:createpersonal: Create a new personal workspace for the specified user
  • ./flow workspace:createshared: Create a new shared workspace
  • ./flow workspace:settitle: Set/change the title of a workspace
  • ./flow workspace:setdescription: Set/change the description of a workspace
  • ./flow workspace:assignrole: Assign a workspace role to the given user/user group
  • ./flow workspace:unassignrole: Unassign a workspace role from the given user/user group
  • ./flow workspace:show: Display details for the specified workspace

Fetching the user workspace

The previous ways of retrieving a workspace or a building a workspace name are replaced:

  • Neos 8.3: Neos\Neos\Utility\User::getPersonalWorkspaceNameForUsername()
  • Neos\Neos\Domain\Service\WorkspaceNameBuilder::fromAccountIdentifier()
  • WorkspaceFinder::getPersonalWorkspaceForUser()

Instead, the new WokspaceService must be used:

+ #[Flow\Inject]
+ protected WorkspaceService $workspaceService;
+ 
+ $user = $this->userService->getBackendUser();
+ $this->workspaceService->getPersonalWorkspaceForUser($contentRepositoryId, $user->getId());

Deprecation of the WorkspaceFinder (removal with next beta)

Using WorkspaceFinder (retrieved via ContentRepository::getWorkspaceFinder) to fetch workspaces is deprecated as a new API will fully replace it in the next beta.

- $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName);
+ $contentRepository->findWorkspaceByName($workspaceName);
- $contentRepository->getWorkspaceFinder()->findAll();
+ $contentRepository->findWorkspaces();
- $contentRepository->getWorkspaceFinder()->findByBaseWorkspace($workspaceName);
+ $contentRepository->findWorkspaces()->getDependantWorkspaces($workspaceName);
- $contentRepository->getWorkspaceFinder()->findOutdated();
+ $contentRepository->getWorkspaces()->filter(
+     fn (Workspace $workspace) => $workspace->status === WorkspaceStatus::OUTDATED
+ );

The use of WorkspaceFinder::findOneByCurrentContentStreamId is discouraged, and it should just be operated on workspace names instead.
If deemed necessary it still can be archived by filtering all workspaces:

- $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId();
+ $contentRepository->getWorkspaces()->find(
+     fn (Workspace $workspace) => $workspace->currentContentStreamId->equals($contentStreamId)
+ );

Deprecation of RenameWorkspace and ChangeWorkspaceOwner commands (removal with next beta)

please use WorkspaceService::setWorkspaceTitle and setWorkspaceDescription instead. Changing the owner is not possible but you grant a user access via assignWorkspaceRole.

Deprecation of title, description and owner in CreateWorkspace and CreateRootWorkspace (removal of fields with next beta)

To create a root, shared or user workspace, the WorkspaceService should be leveraged instead:

  • WorkspaceService::createRootWorkspace()
  • WorkspaceService::createPersonalWorkspace()
  • WorkspaceService::createSharedWorkspace()

To assign a role like Neos.Neos:LivePublisher or Neos.Neos:AbstractEditor to the workspace look into assignWorkspaceRole. The permission will be evaluated fully with the next beta.

$this->workspaceService->assignWorkspaceRole($contentRepositoryId, $workspaceName, WorkspaceRoleAssignment::createForGroup(
    'Neos.Neos:AbstractEditor',
    WorkspaceRole::COLLABORATOR,
));

For granting a user access to a workspace WorkspaceRoleAssignment::createForUser can be used.

Refactoring of Workspace “active record” model

A WorkspacePublishingService was introduced as replacement for the current Neos 9 Workspace model as well as the WorkspaceProvider.
Publishing via Workspace::publishChangesInSite() can be replaced with WorkspacePublishingService::publishChangesInSite() and other use cases as well.

!!! 2.) Overhauled Node field access in Fusion

String-able aggregateId, nodeTypeName, … for improved use in Fusion
To simplify node field access of string value objects in Fusion we made the following fields automatically string casting (using php’s __toString)

  • contentRepositoryId
  • workspaceName
  • aggregateId
  • nodeTypeName
  • name

That means one does not need to specify .value on those fields anymore:

- ${node.name.value}
+ ${node.name}

Deprecation of FlowQuery accessors id() nodeTypeName() and label() (old syntax removal with next beta)

With the node fields being string able, the need for dedicated FlowQueries to simplify the casting of .value is obsolete.
Its recommended to just leverage the Node fields directly. The FlowQuery helpers will be removed with the next beta.

- ${node.identifier} // 8.3
- ${q(node).id()}
+ ${node.aggregateId}
- ${node.nodeType.name} // 8.3
- ${q(node).nodeTypeName()}
+ ${node.nodeTypeName}

Additionally, our rethought philosophy being: Use FlowQuery for traversal and Helpers for direct access, the label retrieval of Nodes is moved to a Helper Neos.Node.label():

- ${node.label} // 8.3
- ${q(node).label()}
+ ${Neos.Node.label(node)}

Rename of helper Neos.Node.getNodeType() (old syntax removal with next beta)

Also, the previous getNodeType() would return a NodeType of Neos.Neos:FallbackNode while nodeType() returns NULL for non-existing NodeTypes.
To check if a NodeType exists - if really necessary - Neos.Node.isNodeTypeExistent() could be leveraged although being @internal.

- ${node.nodeType} // 8.3
- ${Neos.Node.getNodeType(node)}
+ ${Neos.Node.nodeType(node)}

Full removal of node magic underscore _* access: q(node).property('_identifier')

In Neos 8 the _* underscore access syntax can be used to access actual php object fields of the Node.
With the Neos 9 rewrite the Node object looks completely different and has way less getters / fields (comparison).

The only overlap of fields to the old node are _name and the unknown _nodeTypeName.

To avoid confusion about which magic underscore properties still exist in 9.0 - as rule of thumb - None. The syntax will be removed.

Special cases that had yet no equivalent were:

  • Access the Node’s _hidden state can be now done via Neos.Node.isDisabled(node)
  • The sort operation handled in 8.3 also _creationDateTime, _lastModificationDateTime and _lastPublicationDateTime. For Neos 9.0 the new sortByTimestamp(created|lastModified|originalCreated|originalLastModified) FlowQuery can be used.

Simple cases:

- ${q(node).property('_identifier')}
+ ${node.aggregateId}
- ${q(node).property('_name')}
+ ${node.name}

The following information is not directly extractable from the Node in EEL, and thus we use the Neos.Node helper.

- ${q(node).property('_nodeType')}
+ ${Neos.Node.nodeType(node)}
- ${q(node).property('_hidden')}
+ ${Neos.Node.isDisabled(node)}
- ${q(node).property('_path')}
+ ${Neos.Node.path(node)} // @deprecated
- ${q(node).property('_depth')}
+ ${Neos.Node.depth(node)} // @deprecated

A special cases is fetching the path to render a content collection which can be simplified:

content = Neos.Neos:ContentCollection {
-   nodePath = ${q(site).children('footer').property('_path')}
+   @context.node = ${q(site).children('footer').get(0)}
}

Partial deprecation of FlowQuery q(node).property("title")

The property operation was the recommended way in Neos 8 to access properties of a Node. With Neos 9 simple cases of accessing properties can be done with node.properties.title instead as properties don’t contain references anymore and the performance overhead is gone:

- ${q(node).property("title")}
+ ${node.properties.title}

For dynamic access:

- ${q(node).properties(props.propertyName)}
+ ${node.properties[props.propertyName]}

For references - where the property operation implements a thin b/c layer - its recommend to be explicit and use the new referenceNodes operation instead:

- ${q(node).properties("someReferenceName")}
+ ${q(node).referenceNodes("someReferenceName").get(0)}

For accessing a property in a FlowQuery chain - e.g. after fetching the parent - using the property operation is still the recommended way.

!!! 3.) Refactoring of the ControllerInterface and dispatching in FLOW.

To ease the implementation of a custom controller now use a direct pattern of f(ActionRequest) => PsrResponseInterface.
This is breaking if you implemented your own ControllerInterface or overwrote low level parts and methods of the ActionController.

class Dispatcher
{
    public function dispatch(ActionRequest $request): ResponseInterface;
}

interface ControllerInterface
{
    public function processRequest(ActionRequest $request): ResponseInterface;
}

Also, it’s discouraged to manipulate $this->response in controllers (the ActionResponse is deprecated), although it’s still supported in actions for now, please consider returning a new response instead.

Explanation of the legacy MVC response object. Previously Flows MVC needed a single mutable response which was passed from dispatcher to controllers and even further to the view and other places via the controller context: `ControllerContext::getResponse()`. This allowed to manipulate the response at every place. With the dispatcher and controllers now directly returning a response, the mutability is no longer required. Additionally, the abstraction offers naturally nothing, that cant be archived by the psr response, as it directly translates to one: `ActionResponse::buildHttpResponse()`. So you can and should use the immutable psr {@see ResponseInterface} instead where-ever possible. For backwards compatibility, each controller will might now manage an own instance of the action response via `$this->response` `AbstractController::$response` and pass it along to places. But this behaviour is deprecated! Instead, you can directly return a PSR repose `\GuzzleHttp\Psr7\Response` from a controller:
public function myAction(): ResponseInterface
{
    return (new Response(status: 200, body: $output))
        ->withAddedHeader('X-My-Header', 'foo');
}

Further the ForwardException does not extend the StopActionException anymore, meaning a try catch must be adjusted.

4.) Overhaul content cache flusher

The content cache flusher got a tremendous overhaul fixing several problems:

  • the flush is also executed on rebase and further edge cases
  • partially publishing does not lead to dead cache entries and cache flooding
  • performance of the used sql queries was improved resulting in a faster replay

5.) Simplification of Runtime caches

Previously, ContentGraph::getSubgraph() returned an instance of ContentSubgraphWithRuntimeCaches to increase performance of multiple reads in the same process.
Caching database access on such a low level has drawbacks and this caching layer was extracted to ContentRepositoryRegistry::subgraphForNode().
This allowed the getContentGraph() cache to be removed.

7.) Rewrite asset usage as CatchupHook

Overhauls the latest “asset usage” implementation being a dedicated projection.
It was not handling all necessary events and thus prone to bugs.

A rewrite to a catchup hook stabilizes this feature.
The index can be build individually - which might be required after updating and not running a full replay:

./flow assetusage:index --contentrepository default

6.) Other refactorings / improvements

Bugfixes

Neos Content Repository

Flow

Dev only relevant

List of things

Neos

Neos Ui 9.0

Neos Ui 8.4

:bulb: Known issues

Not working ACL for workspaces without metadata

After importing via cr:import or creating a workspace the workspace permissions might not be correct due to a bug.

If you experience issues (especially with shared workspaces) as hotfix you can run this command (adjust the workspace name in the command):

./flow workspace:settitle live "Live workspace"

Alternatively this PR can be used directly as a patch:

{
    "name": "your/distribution",
    "config": {
        "allow-plugins": {
          // ...
          "cweagans/composer-patches": true
        }
    },
    "require": {
      // ...
      "cweagans/composer-patches": "^1.7.3"
    },
    "extra": {
      "patches": {
        "neos/neos": {
          "BUGFIX: Fix admin workspace permissions for workspaces without metadata": "https://github.com/neos/neos-development-collection/pull/5290.patch"
        }
      }
    }
}

Partial publish / rebase breaks change projection

this will be fixed with the next beta

2 Likes