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


(Christopher Hlubek) #1

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:


[SOLVED] Convert Datasource object into its object instance
Rendering data from Entity model to the inspector using DataSource
(Johannes Steu) #2

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.


(Sebastian Helzle) #3

Great guide!

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


(Christopher Hlubek) #4

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.


(Christopher Hlubek) #5

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.


(Sebastian Helzle) #6

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.


(Dominique Feyer) #7

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


(Christopher Hlubek) #8

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


(Dennis Schröder) #9

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”.


(Lorenz Ulrich) #10

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' => '~'];

(Christopher Hlubek) #11

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.


(Dennis Schröder) #12

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


(Bastian Heist) #13

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.


(Benjamin Klix) #14

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


(Christopher Hlubek) #15

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.


(Benjamin Klix) #16

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?


(Lorenz Ulrich) #17

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.


(Christopher Hlubek) #18

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.


(Rene Rehme) #19

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


(Benjamin Klix) #20

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?