Guide: How to select entities in the inspector (Neos 2.3 LTS)

It’s not very obvious how to use entities in node types and make entities
selectable in the inspector for a node property or access entity properties
when using nodes in Fusion. With Neos 2.3 there’s a pretty good way of connecting
the two worlds.

Suppose we have the following entity:

/**
 * @Flow\Entity
 */
class Product {

    /**
     * string
     */
    protected $title;

    // ...
}

And a node type MyPackage:ProductTeaser where we want to reference a product
to display some properties in the node type.

Let’s start with a new property for a Product on the node type in a
NodeType.yaml:

MyPackage:ProductTeaser:
  superTypes:
    'TYPO3.Neos:Content': true
  ui:
    label: 'Product teaser'
  properties:
    product:
      type: 'MyPackage\Domain\Model\Product'
      ui:
        label: 'Product'
        inspector:
          group: 'resources'
          reloadIfChanged: true

We just use the entity type in the node type definition and add a special
setting for Neos on how to render this entity in the HTML markup for the backend
(serialized node information) and specify a default editor using a SelectBoxEditor.

In the package Settings.yaml:

TYPO3:
  Neos:
    userInterface:
      inspector:
        dataTypes:
          'MyPackage\Domain\Model\Product':
            typeConverter: 'TYPO3\Neos\TypeConverter\EntityToIdentityConverter'
            editor: 'TYPO3.Neos/Inspector/Editors/SelectBoxEditor'
            editorOptions:
              dataSourceIdentifier: 'mypackage-products'

We specified a data source (see http://neos.readthedocs.io/en/stable/ExtendingNeos/DataSources.html)
here that will fetch the data for the select box from the server. This needs a
custom PHP class that implements TYPO3\Neos\Service\DataSource\DataSourceInterface:

class ProductSource extends AbstractDataSource
{

    /**
     * @var ProductRepository
     * @Flow\Inject
     */
    protected $productRepository;

    /**
     * @Flow\Inject
     * @var PersistenceManagerInterface
     */
    protected $persistenceManager;

    /**
     * @var string
     */
    protected static $identifier = 'mypackage-products';

    /**
     * @inheritDoc
     */
    public function getData(NodeInterface $node = null, array $arguments)
    {
        // Use [] if you don't want to include an "empty" item
        $options = [['label' => '-', 'value' => '']];

        $products = $this->productRepository->findAll();
        foreach ($products as $product) {
            $options[] = [
                'label' => $product->getTitle(),
                // Yes, we actually need to encode the value to match EntityToIdentityConverter that is used to encode the serialized node property!
                'value' => json_encode([
                    '__identity' => $this->persistenceManager->getIdentifierByObject($product),
                    '__type' => TypeHandling::getTypeForValue($product)
                ])
            ];
        }

        return $options;
    }

}

When rendering a ProductTeaser node you can now access an associated Product
entity after it was set in the inspector:

<div class="product-teaser">
  <h2>{product.title}</h2>
</div>

This works because the property type is set to an actual class and can be
deserialized with the type and identity values converted by EntityToIdentityConverter.

It’s Magic :wink:

13 Likes

Hey @christopher!

Thanks for that snippet. Guess it will help a lot of ppl since this is a common use-case!
I think we can also implement a new feature to search for Products. The ``getData() Method provide always all values. If your data set is huge this might be an performance issue.

2 Likes

Great guide!

But people always have to remember that entities are not included in site exports if they rely on that.

1 Like

I think we should be able to implement that using arguments in getData and having support to supply a standard search argument through an editor. If possible we should not change the PHP type or method signature.

Actually they are serialized to JSON in the export with the PropertyMapper and converted back during the node import. I have to test if this will also restore entities during an import correctly.

Update: Actually it works pretty well, for more complex entities (I had problems with DateTime) you need to have a custom TypeConverter with $targetType = 'array'. See \TYPO3\Media\TypeConverter\ArrayConverter for an example, that’s basically what we do with Assets for a site export / import in Neos.

2 Likes

Oh very cool, I didn’t know that :slight_smile:

Have to check out if I can then fix the issues I had with this in one project.

Nice “howto”, I think when we have good content like this is it deserve a proper blog post, with nice screenshot and some social promotion. And it fit well with the current activity around neos.io

Well, I’m happy to “repost” this to the neos.io blog :wink:

1 Like

Ist it possible to add multiple entities to an node as a property? When I set the editorOptions to `` multiple: true` in the settings.yaml, I get the following error:

Server communication error: 500 Internal Server Error
#1297759968: Exception while property mapping for target type “TYPO3\TYPO3CR\Domain\Model\Node”, at property path “”: Exception while property mapping for target type “ObisConcept\NeosBlogPro\Domain\Model\Category”, at property path “”: Property “0” was not found in target object of type “ObisConcept\NeosBlogPro\Domain\Model\Category”.

In order to add an empty value as first option, you can use this as the first entry in the ```$options` array:

$options[] = ['label' => '', 'value' => '~'];
1 Like

Did you try changing the property type to array<ObisConcept\NeosBlogPro\Domain\Model\Category>?

TYPO3\Neos\TypeConverter\EntityToIdentityConverter does not work with multi-valued properties out of the box, so you have to implement a custom type converter that converts an array of objects to an array of identities (using the same format as EntityToIdentityConverter.

1 Like

Thx for your reply but this did not solve the problem!

Nice Description, thanks! Maybe we can expand this into a guide on how to manage domain data and connect it to content, e.g. by additonally using CrudForms to easily create a backend module with list and CRUD actions.

Can we add this to ReadTheDocs? Because most people have a look there first before searching the forum.

Hi Benjamin,

sure! Would be great to have it in the official docs. Could you imagine helping with this task?

You can find the Neos documentation in https://github.com/neos/neos-development-collection/tree/master/Neos.Neos/Documentation and a contribution howto for our documentation on https://www.neos.io/contribute/documenting-neos.html.

Hey @christopher,

i’ll try to add it if i can get some time in between.

I currently tried to use this example with editorOptions multiple: true, but i’m not able to confirm the changes.
The request to /neos/service/node/update returns this:

{
  "node.0": [
    {
      "severity": "Error",
      "message": "Object of type \"Package\\Domain\\Model\\Entity\" with identity \"{\"__identity\":\"0e743260-4bba-4ee4-87f7-1e6dd3741f31\",\"__type\":\"Package\\\\Domain\\\\Model\\\\Entity\"}\" not found.",
      "code": 1412283033
    }
  ]
}

My Settings.yaml looks sth. like this:

Neos:
  Neos:
    userInterface:
      dataTypes:
        'array<Package\Domain\Model\Entity>':
          typeConverter: 'Neos\Neos\TypeConverter\EntityToIdentityConverter'
          editor: Neos.Neos/Inspector/Editors/SelectBoxEditor
          editorOptions:
            dataSourceIdentifier: 'package-source'
            dataSourceAdditionalData:
          multiple: true

Do you have any idea, what one can do to get multiple values to work?

Does this work for anyone here in Neos 3.3? In my installation, I can select the Entity, but once I click apply, it is lost and not saved to the database.

Update for Neos 3.3 LTS:

The options returned from getData must not be double JSON encoded anymore, so the code gets more straight forward:

class ProductSource extends AbstractDataSource
{

    /**
     * @var ProductRepository
     * @Flow\Inject
     */
    protected $productRepository;

    /**
     * @Flow\Inject
     * @var PersistenceManagerInterface
     */
    protected $persistenceManager;

    /**
     * @var string
     */
    protected static $identifier = 'mypackage-products';

    /**
     * @inheritDoc
     */
    public function getData(NodeInterface $node = null, array $arguments)
    {
        // Use [] if you don't want to include an "empty" item
        $options = [['label' => '-', 'value' => '']];

        $products = $this->productRepository->findAll();
        foreach ($products as $product) {
            $options[] = [
                'label' => $product->getTitle(),
                'value' => [
                    '__identity' => $this->persistenceManager->getIdentifierByObject($product),
                    '__type' => TypeHandling::getTypeForValue($product)
                ]
            ];
        }

        return $options;
    }

}

All the rest works the same way as desribed for Neos 2.3.

3 Likes

I just want to update informations here:

multiple selection is possible and works for me when define “multiple: true” in the nodetype:

  properties:
    product:
      type: array
      ui:
        inspector:
          group: 'settings'
          editor: 'Content/Inspector/Editors/SelectBoxEditor'
          editorOptions:
            multiple: true
            dataSourceIdentifier: 'my-package-product'

note that i don’t use a model for type! Tested in neos 3.2

What does your DataSource return? And if it is an array of entities (identity + type), does it convert to the correct entity, even if the typeConverter is omitted?