Comment plugin: Best practice for creating new (child) nodes from frontend


I’m trying to create a comment function for my Neos page. The manual’s tutorial suggests creating a new repository and model for plugins.
I followed the instructions of the manual.
I created a package, a model and a repository for my comments:

$ ./flow kickstart:package My.Plugin
$ ./flow kickstart:model --force My.Plugin Comment authorName:string authorEmail:string commentDate:\DateTime commentContent:string
$ ./flow kickstart:repository My.Plugin Comment
$ ./flow kickstart:actioncontroller My.Plugin Comment  --generate-actions --generate-templates

Using the auto-generated files which i adjusted a little bit, I am able to create new comments using the plugin. My problem is that I don’t know how to connect a comment to a specific documentNode. Currently every comment will be displayed on every instance of the plugin.
I’m clear that my model doesn’t even have a field in which I could store a reference, but of which type would that reference be?

Looking at some plugins offered on the extensions site they don’t use a model at all. They simply use an ActionController to create the child nodes using a NodeTemplate.
I don’t understand how that magic works at the moment. Could someone explain this approach (maybe @robert, since he created one the solutions [robertlemke/plugin-blog])?
Is there any tutorial on that approach?

Generally speaking: Which way is preferable?
Using a NodeTemplate seems to be the more “Neos-like” way since one can think those comments as nodes. When should a model and a repository be created?

As long as you do’nt have to process the data intensively using nodes ist mostly the most neos-ish way.

Plugins bring custom domain models and controllers that may be crucial in specific use-cases. You can optimize queries a lot more than in the content repository wich is a generic node-store.

In a simple data-in data-out situation i would mostly prefer nodes.

A thing you have to consider is content-dimensions. If you use nodes you get the same dimension configuration as the whole site wich may be a feature or a problem. For plugins you have to handle everything that goes beyond translating labels on your own.

Regards, Martin

Which means: Creating a comment in a content dimension for language A wouldn’t get rendered in a content dimension for language B by default?

I tried the approach to create and persist comments as nodes in the content repository using the createNodeFromTemplate function.

namespace Vendor\PageComments\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Model\NodeTemplate;

class CommentController extends ActionController

 * Creates a new comment
 * @param NodeInterface $feedNode The node which contains the comment feed's ContentCollection that will contain the new comment node
 * @param NodeTemplate<Vendor.PageComments:Comment> $comment Object which holds the properties of the new comment
 * @return string
public function createAction(NodeInterface $feedNode, NodeTemplate $comment)
		# Get the ContentCollection which will contain the comment
		$commentsFeed = $feedNode->getNode('feed');
		# Create the new comment node as child node of the comment ContentCollection
		$commentNode = $commentsFeed->createNodeFromTemplate($comment, uniqid('comment-'));

But this throws an exception:

Exception while property mapping for target type “Neos\ContentRepository\Domain\Model\NodeTemplateVendor.PageComments:Comment”, at property path “”: Node type “unstructured” does not have a property “authorName” according to the schema

The form which is used to create the comment:

<f:form class="ui reply form" action="create" controller="Comment" package="Vendor.PageComments" object="{comment}" objectName="comment" id="newCommentForm">
	<f:form.hidden name="feedNode" value="{node.path}" />
	<div class="two fields">
		<div class="field">
			<label for="authorName">Name</label>
			<f:form.textfield property="authorName" id="authorName" placeholder="Name"/>

		<div class="field">
			<label for="authorEmail">E-Mail</label>
			<f:form.textfield property="authorEmail" id="authorEmail" placeholder="E-Mail"/>
	<div class="field">
		<f:form.textarea property="commentContent" id="commentContent" />
	<f:form.button type="submit" class="ui blue labeled submit icon button">
		<i class="icon edit"></i> Kommentieren

I don’t get why the controller/NodeTemplate/Interface doesn’t recognize the node type. Especially because the exception log contains the node type in line 11.

Exception #1297759968 in line 210 of /var/www/html/stammprojekte/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/Neos_Flow_Mvc_Controller_Argument.php: Exception while property mapping for target type "Neos\ContentRepository\Domain\Model\NodeTemplate<Vendor.PageComments:Comment>", at property path "": Node type "unstructured" does not have a property "authorName" according to the schema

11 Neos\Flow\Property\PropertyMapper_Original::convert(array|3|, "Neos\ContentRepository\Domain\Model\NodeTemplate<Vendor.PageComments:Comment>", Neos\Flow\Mvc\Controller\MvcPropertyMappingConfiguration)
10 Neos\Flow\Mvc\Controller\Argument_Original::setValue(array|3|)
9 Neos\Flow\Mvc\Controller\AbstractController_Original::mapRequestArgumentsToControllerArguments()
8 Neos\Flow\Mvc\Controller\ActionController_Original::processRequest(Neos\Flow\Mvc\ActionRequest, Neos\Flow\Http\Response)
7 Neos\Flow\Mvc\Dispatcher_Original::initiateDispatchLoop(Neos\Flow\Mvc\ActionRequest, Neos\Flow\Http\Response)
6 Neos\Flow\Mvc\Dispatcher_Original::dispatch(Neos\Flow\Mvc\ActionRequest, Neos\Flow\Http\Response)
5 Neos\Flow\Mvc\DispatchComponent_Original::handle(Neos\Flow\Http\Component\ComponentContext)
4 Neos\Flow\Http\Component\ComponentChain_Original::handle(Neos\Flow\Http\Component\ComponentContext)
3 Neos\Flow\Http\Component\ComponentChain_Original::handle(Neos\Flow\Http\Component\ComponentContext)
2 Neos\Flow\Http\RequestHandler::handleRequest()
1 Neos\Flow\Core\Bootstrap::run()

Why does the exception get thrown?

I have not tried it that way. I would use a transfer Object for the comment and map the properties to the nodeTemplate in the controller.