I have an ordninary model called "Mail":
Namespace ...
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Mail extends AbstractEntity
{
/**
* #var string
*/
protected $name;
protected $company;
.../**
* #var string
*/
protected $company;
...
I want to use it in a Form:
<f:form action="post" object="{mail}">
<f:form.textfield property="name"/>
...
</f:form>
First weird thing is, the html the viewhelper generates is:
<input name="tx_myext_offer[name]">
But in order to work it should be:
<input name="tx_myext_offer[mail][name]">
So I try to write the html of the input field manualy with the name attribute like "tx_myext_offer[mail][name]".
When I now send the form to the controller I get an error:
#1297759968: Exception while property mapping at property path "": It is not allowed to map property "name". You need to use $propertyMappingConfiguration->allowProperties('name') to enable mapping of this property.
When I debug the PropertyMappingConfiguration Object of the request I see that the "propertiesNotToBeMapped" Attribute is empty. There should be the attributes of the Mail model.
Somehow extbase does not map it automatically this time. Seems like I missed something somewhere. How can I tell extbase to map the the properties of the Model automatically?
#ThomasLöffler
in the controller Action which calls the form there is nothing exciting happening:
public function showAction()
{
$this->view->assignMultiple(
[
'mail' => $this->objectManager->get(Mail::class)
]
);
}
First thing first. You have missed the attribute objectName="mail" in your <f:form /> tag.
When you add this attribute the hidden field tx_myext_offer[__trustedProperties] and a bunch of others will changes and then your automatic property mapping should work.
Related
I'm trying to use a virtual domain model property in TYPO3 9.5.x that doesn't have a database field representation but I can't get it to work.
My model looks like this
class Project extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* participants
*
* #var string
*/
protected $participants;
...
/**
* Returns the participants
*
* #return string $participants
*/
public function getParticipants()
{
$this->participants = "foo";
return $this->participants;
}
}
I do see the property when I debug the model but it's always null as if it doesn't even recognise the getter method getParticipants().
Any idea what I might be doing wrong?
Already added a database field to ext_tables.sql and the TCA, but it didn't seem to make a difference.
The property is null because that's the state when the Extbase debugger inspects it. Notice that the Extbase debugger knows nothing about getters and also does not call them.
So if you want to initialize your property you must do this at the declaration time:
protected $participants = 'foo';
You can debug this property by simpy accessing it.
In Fluid, if you use <f:debug>{myModel}</f:debug>, you will see NULL for your property.
But if you directly use <f:debug>{myModel.participants}</f:debug>, you will see 'foo'.
I'm building a simple CRUD using symfony 4.
One of my entities is called Color. Nothing fancy about it. It just has 4 properties: name, description, sortOrder and id (PK).
I also built a form class to be able to add/edit my entity instances.
The form looks like this:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
class Color extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('name', TextType::class)
->add('description', TextareaType::class, ['required' => false])
->add('sort_order', IntegerType::class);
}
}
When I try to render the form I get an error
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class App\Entity\Color could not be converted to string").
I have other forms that look similar and they work.
If I don't let symfony decide the block prefix based on the class name and add the method
public function getBlockPrefix()
{
return 'anything-else-but-color';
}
then the form is rendered.
I don't want to do that because my app is kind of "convention" based and I need a match between the entity class name and the form name.
I know I can change both the entity and the form class names (and I'm probably going to do that) but this bugs me because I don't know what happens and if there are any other reserved words that might screw up my app.
Additional info...
I can partially debug this.
I see that when the form is rendered something like this is generated
<input type="color" id="color" name="color" class="form-control" ....
(notice the type color).
For a different entity and form built in the same way (named group) I get this rendered in the same place as above.
<div id="group">...
So my questions are:
Are there any reserved words that cannot be used for getBlockPrefix in a form?
Can I use the word color as a block prefix without jumping through many hoops?
The documentation touches on this:
When the name of your form class matches any of the built-in field
types, your form might not be rendered correctly. A form type named
App\Form\PasswordType will have the same block name as the built-in
PasswordType and won't be rendered correctly. Override the
getBlockPrefix() method to return a unique block prefix (e.g.
app_password) to avoid collisions.
I would have expected this to trigger if your form type was called ColorType however, for completeness sake as per your comment:
I think it crashes for me because the Type at the end of the class
name is ignored.
StringUtil::fqcnToBlockPrefix('Symfony\Component\Form\Extension\Core\Type\ColorType');
and StringUtil::fqcnToBlockPrefix('Whatever\YouWant\Color'); return
the same thing: color
So currently i'm working on a TYPO3 extension using a Relation (1:n) to a specific class that provides objects to the main class. Let me explain a bit more in detail: I have a class, that next to normal properties, has a relation to a class that provides objects for some kind of checklist. The idea is that the FE user should be able to add new checklist entries, that's why i use that kind of concept. Initially it would look like this for the edit Action (the [x] represent that it's just a checklist):
main class
property of main class of type boolean [x]
attached object with a string and a boolean property [x]
attached object with a string and a boolean property [x]
property of main class of type text (for comments)
The FE user should be able to edit the record, so he should set the checklist entires to true or false, but even though the checklist entries appear, even those of the attached objects, they aren't persisted, only the properties of the main class are persisted when the FE user edits the records in the FE.
Here is my code:
The domain model of the main class (just the lines that specify the relation to the class that "provides" the objects). Keep in mind that few objects of the related class are attached to the main class when a record of the main class is created, the specific code just isn't in the main classe's controller...
/**
* zugewCheckobject
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Kundentermine\Domain\Model\Checkobject>
* #cascade remove
*/
protected $zugewCheckobject = null;
public function __construct()
{
$this->zugewCheckobject = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Initializes all ObjectStorage properties
* Do not modify this method!
* It will be rewritten on each save in the extension builder
* You may modify the constructor of this class instead
*
* #return void
*/
protected function initStorageObjects()
{
$this->zugewCheckobject = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Adds a Checkobject
*
* #param \...\Kundentermine\Domain\Model\Checkobject $zugewCheckobject
* #return void
*/
public function addZugewCheckobject(\...\Kundentermine\Domain\Model\Checkobject $zugewCheckobject)
{
$this->zugewCheckobject->attach($zugewCheckobject);
}
/**
* Removes a Checkobject
*
* #param \...\Kundentermine\Domain\Model\Checkobject $zugewCheckobjectToRemove The Checkobject to be removed
* #return void
*/
public function removeZugewCheckobject(\...\Kundentermine\Domain\Model\Checkobject $zugewCheckobjectToRemove)
{
$this->zugewCheckobject->detach($zugewCheckobjectToRemove);
}
/**
* Returns the zugewCheckobject
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Kundentermine\Domain\Model\Checkobject> $zugewCheckobject
*/
public function getZugewCheckobject()
{
return $this->zugewCheckobject;
}
/**
* Sets the zugewCheckobject
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Unilab\Kundentermine\Domain\Model\Checkobject> $zugewCheckobject
* #return void
*/
public function setZugewCheckobject(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $zugewCheckobject)
{
$this->zugewCheckobject = $zugewCheckobject;
}
The edit and update Actions in the controller of the main class...
public function editAction(\...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch)
{
$this->view->assign('kaufmnnisch', $kaufmnnisch);
}
/**
* action update
*
* #param \...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch
* #return void
*/
public function updateAction(\...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch)
{
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
$checkobjectRepository = $objectManager->get(\...\Kundentermine\Domain\Repository\CheckobjectRepository::class);
foreach($kaufmnnisch->getZugewCheckobject() as $chk){
$checkobjectRepository->update($chk);
}
$this->kaufmnnischRepository->update($kaufmnnisch);
$this->redirect('list');
}
Part of the "edit" template. I pass the object "kaufmnnisch" as arguments to a partial of the class Chekobject (as it has objects of that class attached to it) and of course to a partial of the class Kaufmnnisch...
<f:section name="main">
<h1>Edit Kaufmnnisch</h1>
<f:flashMessages />
<f:render partial="FormErrors" arguments="{object:Kaufmnnisch}" />
<f:form action="update" name="kaufmnnisch" object="{kaufmnnisch}" >
<f:render partial="Checkobject/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:render partial="Kaufmnnisch/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:form.submit value="Save" />
</f:form>
</f:section>
Again: The problem is that only the properties of the main class get edited not those of the related objects. My assumption is that the Edit template or the partials aren't correctly set.
Edit: Here is the code for the partial of the Checkobject class that gets the object Kaufmnnisch as an argument passed.
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:for each="{kaufmnnisch.zugewcheckobject}" as="chk" >
<f:form.checkbox property="checked" value="0" /><br />
</f:for>
</html>
So basically "zugewcheckobject" is my object storage. Each checkbox gets rendered in the Edit template (thanks to the for each viewhelper), but the values for the boolean properties of each Checkobject object aren't persisted if the user clicks on the "save" button.
Edit2: A var_dump within the updateAction (within the foreach loop) reveals that the "Checked" property of each Checkobject object isn't changed, so it's not only not persisted, but the changed values aren't passed from the Edit template to the updateAction.
Edit3: Acessing every Checkobject object (here: $chk) via the objectstorage "zugewcheckobject" of the passed object of the class "Kaufmnnisch" in the Edit template is possible though, i just cant change the specific properties of the Checkobject objects, because JUST the Kaufmnnisch object is passed back to the updateAction of the specific controller, not each Checkobject of the objectstorage ("zugewcheckobject").
As i understand an objectstorage just contains references to its corresponding objects, not the objects themselfs, negating any changes that were made to them in the view.
In short: How can i also pass the corresponding objectstorage objects to the updateAction in one go for them to be persisted?
Edit4: It also doesn't seem to work with that solution...
<f:form action="update" name="kaufmnnisch" object="{kaufmnnisch}" >
<f:for each="{kaufmnnisch.zugewCheckobject}" as="chk" key="chkIndex" >
<f:form.hidden property="zugewCheckobject.{chkIndex}.__identity" value="{chk.uid}" />
<f:form.checkbox property="zugewCheckobject.{chkIndex}.checked" value="{chk.checked}" /><br />
</f:for>
<f:render partial="Kaufmnnisch/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:form.submit value="Save" />
</f:form>
The problem was solved here: TYPO3 extension thought experiment: Editing a news entry and the comment entries in one go?
update 31.01.
Meanwhile I made a new test Ext as written on Marcels webpage:
http://lbrmedia.net/codebase/Eintrag/extbase-bidirektionale-mm-relation/
I have the same Issue with that.
The goal ist that a Feuser (logged-in) can see all available clients and pick his favorites. So every Feuser can have many clients and each client can be picked by many Feusers. The table relations are correct. In the backend I can see and update the relations on every side (clients can pick Feusers from the list and vice versa.
Please find all the data needed in my Gist:
https://gist.github.com/metaxos/91622c536588d0aa8440
The ZIP of the extension can be found here: http://www.filedropper.com/testmm000201501311251
initial question
For a small Extbase extension. I have a m:n connection between Feusers and Clients. Each Feuser can have multiple clients associated. I have build the relation with Extension Builder and in the backend everything works as needed.
In my frontend plugin I can also add clients to my feuser with attach (this works).
Every Feuser can see all clients (this works). In the for loop of all clients I want to show an icon if the client is associated with the feuser (this works).
But how can i avoid the inner for loop (see below)?
<f:for each="{feusers.clients}" as="singleClient">
Feusers model:
/**
* Clients
*
* #lazy
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<Exapoint\Exaibbrplus\Domain\Model\Clients>
*/
protected $clients;
/**
* __construct
*/
public function __construct() {
$this->initStorageObjects();
}
/**
* Initializes all ObjectStorage properties.
*
* #return void
*/
protected function initStorageObjects() {
$this->clients = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Returns the clients
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Exapoint\Exaibbrplus\Domain\Model\Clients> $clients
*/
public function getClients() {
return $this->clients;
}
List.html:
<f:for each="{clients}" as="client">
<tr>
<td>
<f:for each="{feusers.clients}" as="singleClient">
<f:if condition="{client.identifier}=={singleClient.identifier}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
</f:for>
</td>
</tr>
</f:for>
Relation:
Here's the dump of my clients of feusers:
In my opinion, if a FrontendUser can have n clients, but a client can have only one FrontendUser, you should have used an 1:n relation.
Somehow, your code is very distracting: You iterate through {feusers.clients}, but you can only do that if feusers is in fact not an ObjectStorage of FrontendUsers, but a single FrontendUser. Maybe you could post more of your setup.
A suggest you add a transient property to your Clients model, e.g.
/**
* #var boolean
* #transient
*/
protected $isChildOfAuthenticatedUser`
For this property you create a getter. The getter compares if the currently authenticated user is identical to the parent user of the client. In the example I'm assuming that a Client only has one parent/FrontendUser:
/**
* #return boolean
*/
public function $isChildOfAuthenticatedUser {
if ((int)$GLOBALS['TSFE']->fe_user->user['uid'] === $this->frontendUser->getUid()) {
return TRUE;
} else {
return FALSE;
}
}
Then, in your Fluid template, you just need to check on that transient property:
<f:if condition="{client.isChildOfAuthenticatedUser}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
Keep in mind to add some additional check if the data is also to be displayed if no user is authenticated.
In Spring 3, I have seen two different attribute in form tag in jsp
<form:form method="post" modelAttribute="login">
in this the attribute modelAttribute is the name of the form object whose properties are used to populate the form. And I used it in posting a form and in controller I have used #ModelAttribute to capture value, calling validator, applying business logic. Everything is fine here. Now
<form:form method="post" commandName="login">
What is expected by this attribute, is it also a form object whose properties we are going to populate?
If you look at the source code of FormTag (4.3.x) which backs your <form> element, you'll notice this
/**
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
*/
public void setModelAttribute(String modelAttribute) {
this.modelAttribute = modelAttribute;
}
/**
* Get the name of the form attribute in the model.
*/
protected String getModelAttribute() {
return this.modelAttribute;
}
/**
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
* #see #setModelAttribute
*/
public void setCommandName(String commandName) {
this.modelAttribute = commandName;
}
/**
* Get the name of the form attribute in the model.
* #see #getModelAttribute
*/
protected String getCommandName() {
return this.modelAttribute;
}
They are both referring to the same field, thus having same effect.
But, as the field name indicates, modelAttribute should be preferred, as others have also pointed out.
OLD WAY = commandName
...
<spring:url value="/manage/add.do" var="action" />
<form:form action="${action}" commandName="employee">
<div>
<table>
....
NEW WAY = modelAttribute
..
<spring:url value="/manage/add.do" var="action" />
<form:form action="${action}" modelAttribute="employee">
<div>
<table>
..
I had the same question a while ago, I can't remember the exact differences but from research I ascertained that commandName was the old way of doing it and in new applications you should be using modelAttribute
commandName = name of a variable in the request scope or session scope that contains the information about this form,or this is model for this view. Tt should be a been.
In xml based config, we will use command class to pass an object between controller and views. Now in annotation we are using modelattribute.