ExtbaseObject with relation to multiple occurences of the same object - typo3

I've got an ExtbaseObject with a relation that can contain multiple references to the same subobject.
I've extended the mm table for the relation with a uid field and set the option MM_hasUidField in the tca. In the backend everything works as intended:
But if I load the object in the frontend I get only one occurence of "Testzusatzpaket 1":
The tca configuration in question is:
'zusatzpakete' => [
'label' => 'LLL:EXT:ned_beratung/Resources/Private/Language/locallang_db.xlf:tx_nedberatung_domain_model_beratung.zusatzpakete',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_nedshop_domain_model_artikel',
'multiple' => true,
'maxitems' => 99,
'MM' => 'tx_nedberatung_beratung_zusatzpakete_mm',
'MM_hasUidField' => true,
],
],
In the model, the object is defined as an object storage:
/**
* zusatzpakete
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\NED\NedShop\Domain\Model\Artikel>
* #cascade remove
*/
protected $zusatzpakete = null;
/**
* Adds a Zusatzpaket
*
* #param \NED\NedShop\Domain\Model\Artikel $zusatzpakete
* #return void
*/
public function addZusatzpakete(\NED\NedShop\Domain\Model\Artikel $zusatzpakete)
{
$this->zusatzpakete->attach($zusatzpakete);
}
/**
* Removes a Zusatzpakete
*
* #param \NED\NedShop\Domain\Model\Artikel $zusatzpaketeToRemove The Zusatzpakete to be removed
* #return void
*/
public function removeZusatzpakete(\NED\NedShop\Domain\Model\Artikel $zusatzpaketeToRemove)
{
$this->zusatzpakete->detach($zusatzpaketeToRemove);
}
/**
* Returns the Zusatzpakete
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\NED\NedShop\Domain\Model\Artikel> $zusatzpakete
*/
public function getZusatzpakete()
{
return $this->zusatzpakete;
}
/**
* Sets the Zusatzpakete
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\NED\NedShop\Domain\Model\Artikel> $zusatzpakete
* #return void
*/
public function setZusatzpakete(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $zusatzpakete)
{
$this->zusatzpakete = $zusatzpakete;
}
Why does this not work in the frontend, what am I missing?

That's the way the Extbase ObjectStorage works. It retrieves the object hash of each object and uses this to only store objects uniquely. And the Extbase persistence ensures that every object is only created once by storing it in the persistence session after retrieval.
One option to bypass this is to promote your MM table to a 1st level table including TCA and an own domain model. This way each relation is a separate object and thus unique which allows you to have multiple relations to the same Artikel. And example of such a promoted MM table is the sys_file_reference table in TYPO3.
Your Beratung model would then have a multi-valued property (ObjectStorage) typed to the newly introduced relation model. That model then needs a property to represent the related Artikel.
If you do this you can still keep the field names in your MM table if you want to continue maintaining it with the current setup. Then you only need to change your model relations as described. However if you want to have more descriptive names in your table, you can rename the fields but then it cannot be managed as MM table in TYPO3 anymore so you'd need to change your base table field type e.g. to inline.
One final suggestion: it is general consensus to use English for everything in code including model and property names. This makes it a lot easier for others to get into your code and blends well with the English of the programming language.

Related

TYPO3 TCA type select in FLUID?

I use for the T3 Backend a TCA type select in a renderType = selectMultipleSideBySide
Here the TCA Code:
'features' => array(
'label' => 'Zusatz',
'config' => array(
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'size' => 10,
'minitems' => 0,
'maxitems' => 999,
'items' => array(
array(
'Parkplätze',
'parking'
),
array(
'Freies Wlan',
'wlan'
),
)
)
),
it works fine in the Backend!
But, how can I read the data properly now?
I dont now the right way for the Domain/Model.
/**
* Features
*
* #var string
*/
protected $features = '';
/**
* Returns the features
*
* #return string $features
*/
public function getFeatures() {
return $this->features;
}
/**
* Sets the features
*
* #param string $features
* #return void
*/
public function setFeatures($features) {
$this->features = $features;
}
the Debug Code put out: features => 'parking,wlan' (12 chars)
a for each dont work:
<f:for each="{newsItem.features}" as="featuresItem">
{featuresItem}<br />
</f:for>
thanks for help!
You need to explode the string by comma, so you'll be able to iterate them in the loop, there are at least two approaches: one is custom ViewHelper, second (described bellow) is transient field in your model, when you'll get the IDs of features, you need also to "translate" it to human readable label...
In your model that holds the features, add the transient field with getter like in the sample below: (of course you may delete these boring comment in the annotation but you must to keep the #var line with proper type!):
/**
* This is a transient field, that means, that it havent
* a declaration in SQL and TCA, but allows to add the getter,
* which will do some special jobs... ie. will explode the comma
* separated string to the array
*
* #var array
*/
protected $featuresDecoded;
/**
* And this is getter of transient field, setter is not needed in this case
* It just returns the array of IDs divided by comma
*
* #return array
*/
public function getFeaturesDecoded() {
return \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->features, true);
}
As mentioned before, you need to get human readable labels for the ID, even if you don't build multi-lingual page translation file are very useful, just in the file typo3conf/ext/yourext/Resources/Private/Language/locallang.xlf add items for each feature you have in your TCA select:
<trans-unit id="features.parking">
<source>Parkplätze</source>
</trans-unit>
<trans-unit id="features.wlan">
<source>Freies Wlan</source>
</trans-unit>
as you can see id's of the trans-units after the dot are the same as the keys of your features from TCA,
finally all you need to use this in the view is iterating the transient field instead of original one:
<f:for each="{newsItem.featuresDecoded}" as="feature">
<li>
Feature with key <b>{feature}</b>
it's <b>{f:translate(key: 'features.{feature}')}</b>
</li>
</f:for>
After adding new fields into model (even if they're transient) and adding or changing entries in localization files you need to clear the System cache! in 6.2+ this option is available in the install tool (but you can set it available via UserTS under the flash icon).
Note: the pretty same thing can be done with custom ViewHelper, but IMHO transient field is easier.

Simple date with Sonata Admin Bundle

I have a form with Sonata Admin Bundle with a date, to set the birthday of the user we want to add. Here goes MemberAdmin.php :
/**
* #param \Sonata\AdminBundle\Form\FormMapper $formMapper
*
* #return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('username')
->add('name')
->add('surname')
->add('birthdate', 'birthday', array('format' => 'yyyy-MM-dd'))
// ...
}
And my problem is when I send the form, I obtain Error: Call to a member function format() on a non-object ... But if I do print_r($birthdate) in the Entity class it shows me the DateTime object ...
Here are the interesting Entity parts:
/**
* #var date
*
* #ORM\Column(name="birthdate", type="date", nullable=true, options={"default" = "1990-01-01 00:00:00"})
* #Assert\DateTime()
*/
private $birthdate;
/**
* Set birthdate
*
* #param \DateTime $birthdate
* #return Membre
*/
public function setBirthdate($birthdate)
{
$this->birthdate = $birthdate;
return $this;
}
/**
* Get birthdate
*
* #return \DateTime
*/
public function getBirthdate()
{
return $this->birthdate;
}
My problem, currently, is that I don't know what I should do, I just want the date, no time, no anything else, i don't know if the column should be date (I work with PostgreSQL). What should I use for the types of my variables, I feel lost here, no simple Date possible ??
I tried to figure out from where it could come, but when I change too much I end up with: This form should not contain extra fields directly in the form, or even Incorrect value, but the field is a valid date ...
Thanks for your help !!
Change your field type to sonata_type_date_picker and test if the error message persist.
->add('birthdate', 'sonata_type_date_picker', array(
'format' => 'dd/MM/yyyy',
'widget' => 'single_text',
'label' => 'Birthdate',
))
From manual (sonata-project.org) :
If no type is set, the Admin class will use the one set in the
doctrine mapping definition.
So, you can try this:
->add('birthdate', null, array('format' => 'yyyy-MM-dd'));
#wr0ng.name you should never overwrite vendor code. NEVER.
There is something wrong with your mapping somewhere. You can use doctrine's commands to check your entity.
Edit
As #rande said, modifying vendors files is not the way to go, it provided an easy temp workaround for a local private app. As it is not dedicated to stay like that, I took care of the issue once I had more time. Sorry for the delay to come back to you guys.
I played around, tried with multiple setups, it took me time to figure it out, but I finally came to the conclusion that the issue... was caused by another date, that I was generating wrong in the constructor one line above.
Also, thanks to all of you, that guided me on the right path!

Symfony using DiscriminatorColumn with formbuilder

I have a user entity:
/**
* Class User
* #package Somepackage
* #ORM\Entity(repositoryClass="Somepackage\UserBundle\Entity\Repository\UserRepository")
* #ORM\Table(name="user")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"usereins" = "UserEins", "userzwei" = "UserZwei", "admin" = "Admin"})
*/
class User implements UserInterface, \Serializable {
Now I want to create a form with formbuilder, where a new user can be created and the "discr" can be selected via dropdown. But if I try this:
$builder
->add('discr', 'choice', array( ... ), 'required' => true ));
then Symfony says:
Neither the property "discr" nor one of the methods "getDiscr()", "isDiscr()", "hasDiscr()", "__get()" exist
How can I do this? Is it possible? I have been googling for over an hour and I cannot seem to find anything except other stackoverflow questions no one has been able to answer.
Cerads answer is correct, thank you very much. I added another field to my table in which I store the type of user, so I didn't have to worry about the discr field.

Symfony2 Form: Tags Collection does not get persisted

I have an Upload Entity , that can have many Tags,
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="uploads")
*/
protected $tags;
and a Tag can be in Many Uploads
/**
* #ORM\ManyToMany(targetEntity="Upload", inversedBy="tags")
* #ORM\JoinTable(name="upload_tag")
*/
protected $uploads;
i have a Form, where i can upload a file, and select tags with multi-select....here is a snippet from the UploadType file
......other form elements.....
$builder->add('tags', 'entity', array(
'multiple' => true,
'property' => 'name',
'class' => 'BoiMembersBundle:Tag',
));
The forum submits fine, without errors.....but when i look into my upload_tag, which represents the ManyToMany relationship in my mysql DB, i see no new lines!!!
So the application does not report any errors what so ever..other form elements of Upload get insterted fine, and forwards to the "success"-Route, but i do not see persistanse for the tags.
It's because Upload it's not the owner of the relation with Tag and you're persisting Upload with new entities (of Tag type) inside the relation itself. In fact, it has the mappedBy attribute.
You can configure the cascade option:
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="uploads", cascade={"persist"})
*/
protected $tags;
Or make Upload the owner of the relation (if you think you'll never persist Tag entity with a new Upload inside it):
class Upload
{
/**
* BIDIRECTIONAL - OWNING SIDE
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="uploads")
* #ORM\JoinTable(name="upload_tag")
*/
protected $tags;
}
class Tag
{
/**
* BIDIRECTIONAL - INVERSE SIDE
* #ORM\ManyToMany(targetEntity="Upload", mappedBy="uploads")
*/
protected $uploads;
}
See Working with Associations on Doctrine 2.x documentation.

Symfony2: 1 form to edit a translatable entity

I have a translatable entity using the translatable behaviour of doctrine2.
I'm trying to build a form that looks like this:
| French |English| Spanish |
+--+--------| |---------+------------+
| |
| name: [___my_english_name___] |
| |
| title: [___my_english_title__] |
| |
+------------------------------------------+
Order: [___1___]
Online: (x) Yes
( ) No
So basically, there are the order & online attributes of the object that are not translatable, and the name & title attribute that have the translatable behaviour.
In case my drawing is not clear: the form contain a 1 tab per locale that hold the field that are translatable.
The problem I have is that by default, Symfony2 bind a form to an entity, but the doctrine translatable behaviour force me to have one entity per locale. Personally the doctrine behaviour is fine (and I like it), but I'm unable to make a form that allow me to edit the entity in all the locale -- in the same form.
So far, I've the main form:
namespace myApp\ProductBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
/**
* Form for the productGroup.
*/
class ProductType extends AbstractType
{
/**
* Decide what field will be present in the form.
*
* #param FormBuilder $builder FormBuilder instance.
* #param array $options Custom options.
*
* #return null;
*/
public function buildForm(FormBuilder $builder, array $options)
{
//Todo: get the available locale from the service.
$arrAvailableLocale = array('en_US', 'en_CA', 'fr_CA', 'es_ES');
//Render a tab for each locale
foreach ($arrAvailableLocale as $locale) {
$builder->add(
'localeTab_' . $locale,
new ProductLocaleType(),
array('property_path' => false, //Do not map the type to an attribute.
));
}
//Uni-locale attributes of the entity.
$builder
->add('isOnline')
->add('sortOrder');
}
/**
* Define the defaults options for the form building process.
*
* #param array $options Custom options.
*
* #return array Options with the defaults values applied.
*/
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'myApp\ProductBundle\Entity\Product',
);
}
/**
* Define the unique name of the form.
*
* #return string
*/
public function getName()
{
return 'myapp_productbundle_producttype';
}
}
And the tab-form:
<?php
namespace MyApp\ProductBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use invalidArgumentException;
/**
* Form for the productGroupLocale tabs.
*/
class ProductLocaleType extends AbstractType
{
/**
* Decide what field will be present in the form.
*
* #param FormBuilder $builder FormBuilder instance.
* #param array $options Custom options.
*
* #return null;
*/
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name', 'text', array('data' => ???));
$builder->add('title', 'text', array('data' => ???));
}
/**
* Define the defaults options for the form building process.
*
* #param array $options Custom options.
*
* #return array Options with the defaults values applied.
*/
public function getDefaultOptions(array $options)
{
return array(
//'data_class' => 'MyApp\ProductBundle\Entity\Product',
'name' => '',
'title' => '',
);
}
/**
* Define the unique name of the form.
*
* #return string
*/
public function getName()
{
return 'myapp_productbundle_productlocaletype';
}
}
But as you can't see, I've no idea how to get the name and title values from the translated entity, and neither I know how to persist them once the form will be submitted.
Hi if you use gedmo extensions Translatable is not meant to handle multiple translations per request. Try using knplabs alternative may be a better option to handle it in more general ways.
You may be interested in TranslationFormBundle, which add a form type to work with DoctrineTranslatable extension.
I've check the Translator extension, and even if it's interesting, it wasn't corresponding to our needs. (Basically, all the examples we found require that we change the site locale in order to edit an entity in another locale. I don't know Chinese, and I don't want my interface to be in Chinese, but I do have a translation that I have to copy/paste. Seems weird to explain that as it's really basic in every solid CMS you'll find out there, but I was looking a bit complex to do that kind of CMS functionnality using Symfony.)
So we've developed a solution and builded a BreadGeneratorBundle that we've decide to share:
https://github.com/idealtech/BreadGeneratorBundle
At the time of posting this, it still under development, but it can be used as an alternative to the CrudGenerator in order to generate form for translatable entity.
We also manage to use the Gedmo Extension -- even if Gediminas said it's not meant to handle multiple translation ;)
Hope this will help someone ! :)