Creating and updating eZ Publish Content via Symfony's Form component

By Petar Španja -

Recently we were asked by one of our customers to implement user registration that will not rely on the Legacy Stack. As the sizeable part of this request is related to the user interface, we took this chance to implement something that will benefit us for other use cases as well.

Enter NetgenEzFormsBundle:

NetgenEzFormsBundle is an integration of eZ Publish 5 Repository API with Symfony's well-known Form component. Initial implementation was dictated by the requirements of our use case at hand, so at the moment of writing the bundle supports following forms:

  • Content creation
  • Content updating
  • User creation
  • User updating

It also supports following field types, present in the standard User ContentType:

  • Image
  • TextBlock
  • TextLine
  • User

Care was taken that additional forms can be implemented and missing field types can be supported. In the bundle you will find a demo controller and templates that showcase the usage. Let's just go through some of the basics.

First, you will need to get the form builder. The form will be created dynamically, but the builder is needed to create control buttons, as the bundle does not concern itself with it. The default controller does not provide a way to fetch named form builder, so we have to go over the container:

$formBuilder = $this->container
    ->get( "form.factory" )
    ->createBuilder( "ezforms_create_content", $data );

The first parameter of the createBuilder() method is, of course, the name of the form type. It corresponds with the supported forms like this:

  • ezforms_create_content Content creation
  • ezforms_update_content Content updating
  • ezforms_create_user User creation
  • ezforms_update_user User updating

The second parameter $data is an instance of the DataWrapper class, which is used to wrap eZ Publish data structures. It carries three distinct values:

$data = new DataWrapper( $payload, $definition, $target );

Types of all three constructor arguments are mixed, and, in fact, depend on the context, which is the type of the form used. For example, when creating new Content for the first time, $payload will be ContentCreateStruct and $definition will be corresponding ContentType, but $target does not exist yet and, therefore, is not given:

$contentType = $contentTypeService()->loadContentTypeByIdentifier( "folder" );
$contentSreateStruct = $contentService->newContentCreateStruct( $contentCreateStruct, $contentType );

$data = new DataWrapper( $contentCreateStruct, $contentType );

$formBuilder = $this->container
    ->get( "form.factory" )
    ->createBuilder( "ezforms_create_content", $data );

On the other side, when updating existing Content, you will need to provide it as a $target in order to update it:

$content = $contentService->loadContent( 42 );
$contentType = $contentTypeService()->loadContentType( "article" );
$contentUpdateStruct = $contentService->newContentUpdateStruct();

$data = new DataWrapper( $contentUpdateStruct, $contentType, $content );

$formBuilder = $this->container
    ->get( "form.factory" )
    ->createBuilder( "ezforms_update_content", $data );

You get the idea? It is very similar for Users and should provide support for any other domain implemented in the future.

From here you proceed as is usual with Form component. You can use the payload given in the $data after the request is handled and the form data will be automatically bound to it:

// Adding controls as EzFormsBundle does not do that by itself
$formBuilder->add( "save", "submit", array( "label" => "Update" ) );

$form = $formBuilder->getForm();
$form->handleRequest( $request );

if ( $form->isValid() )
{
    $repository->beginTransaction();

    $contentDraft = $contentService->createContentDraft( $content->contentInfo );
    $contentUpdateStruct = $data->payload;
    
    $contentDraft = $contentService->updateContent(
        $contentDraft->versionInfo,
        $contentUpdateStruct
    );
    $content = $contentService->publishVersion( $contentDraft->versionInfo );

    $repository->commit();
        
    return $this->redirect(
        $this->generateUrl(
            $this->getRepository()->getLocationService()->loadLocation(
                $content->contentInfo->mainLocationId
            )
        )
    );
}

return $this->render(
    "MyBundle::template.html.twig",
    array(
        "form" => $form->createView(),
    )
);

In the case of more complex field types, the form templates will be provided with all necessary data for rendering the field in the standard eZ Publish way. At the moment, this is used with the Image field type, for which the existing image will be rendered inside the update form. I will not go over the specifics of the field type handling here, but you can find the example usage in the demo templates provided with the bundle.

Note that the bundle's only responsibility is building forms and binding form data to and from eZ Publish data structures. It does not even create control buttons, let alone provide the mechanisms to update the draft and publish separately and so on. You will probably find the need to systematise the logic around your forms, but that is intentionally not the concern of this bundle.

That’s all for the first time. I hope you will find it useful, and, of course, share your improvements on what is still a very limited functionality.

Comments

This site uses cookies. Some of these cookies are essential, while others help us improve your experience by providing insights into how the site is being used. For more detailed information on the cookies we use, please check our Privacy Policy.