Extbase ObjectStorage returns a hashcode. But why? - typo3

I was just extending an existing Typo3 4.7 Extension with two own Model classes.
It runs quite well, Backendforms look like expected BUT when I try to access some SubObjects of my Model Classes in the templates via Object Accessor {class.subclass.attribute} I am not able to access the attribute. The problem that showed me, is that the Object for the Attribute "mainColor" for example in the Object Storage is a HashCode, which contains the Actual Object I want to access ( the object following the hashcode is the correct related object from the database ).
Does anyone of you have an Idea where the problem might be ?
If any more Code Snippets are needed, I will deliver them. But since I really don't know where the problem comes from, I prefer to not deliver a wall of Code.
Domain/Model/Cluster.php
/**
* Main Color of Cluster
* #var Tx_Extbase_Persistence_ObjectStorage<Tx_Alp_Domain_Model_ColorCombination> $mainColor
*/
protected $mainColor;
/**
* Subcolors of Cluster
* #var Tx_Extbase_Persistence_ObjectStorage<Tx_Alp_Domain_Model_ColorCombination> $subColors
*/
protected $subColors;
/**
* Constructor
* #return void
*/
public function __construct() {
$this->initStorageObjects();
}
/**
* Initializes all Tx_Extbase_Persistence_ObjectStorage properties.
* #return void
*/
protected function initStorageObjects() {
$this->subColors = new Tx_Extbase_Persistence_ObjectStorage();
$this->mainColor = new Tx_Extbase_Persistence_ObjectStorage();
}
TCA/Cluster.php
'sub_colors' => array(
'exclude' => 1,
'label' => 'Sub-Colors',
'config' => array(
// edited
'type' => 'inline',
'internal_type' => 'db',
'allowed' => 'tx_alp_domain_model_colorcombination',
'foreign_table' => 'tx_alp_domain_model_colorcombination',
'MM' => 'tx_alp_cluster_subcolorcombination_mm',
'MM_opposite_field' => 'parent_cluster',
'size' => 6,
'autoSizeMax' => 30,
'maxitems' => 9999,
'multiple' => 0,
'selectedListStyle' => 'width:250px;',
'wizards' => array(
'_PADDING' => 5,
'_VERTICAL' => 1,
'suggest' => array(
'type' => 'suggest',
),
),
),
),
Fluid Debug Output can be found here:
http://i60.tinypic.com/28kluub.jpg
Thanks for any help :(
And sorry for my bad English and this is my first question here, hope I did it right ;)

Unless you have a 1:1 relation from a model to a sub model, you cannot access the sub model because the sub model is an ObjectStorage.
Example:
Domain/Model/Cluster.php
/**
* Main Color of Cluster
* #var Tx_Alp_Domain_Model_ColorCombination $mainColor
*/
protected $mainColor;
This means that the Cluster model has exacly one main color (mind the annotation), this is a 1:1 relation.
Therefore using {cluster.mainColor.property} will work.
What you are doing is:
Domain/Model/Cluster.php
/**
* Main Color of Cluster
* #var Tx_Extbase_Persistence_ObjectStorage<Tx_Alp_Domain_Model_ColorCombination> $mainColor
*/
protected $mainColor;
This means that every Cluster can have multiple main colors, this is a 1:n relation. Therefore you must iterate through the mainColors (and call the property $mainColors):
<f:for each="{cluster.mainColors}" as="mainColor">
{mainColor.property}
</f:for>

Related

TYPO3 Extbase how to empty ObjectStorage

I want to "empty" an ObjectStorage when updating a Object:
It's TYPO3 4.6 with a Extbase Extension which allows you to show/add/edit/delete datasets in the frontend. At first sight everything looks good.
I have one field referencing another table:
TCA:
'partner' => array(
'exclude' => 0,
'label' => 'LLL:EXT:toco3_marketingdb/Resources/Private/Language/locallang_db.xlf:tx_toco3marketingdb_domain_model_firma.partner',
'config' => array(
'type' => 'select',
'size' => 5,
'foreign_table' => 'tx_toco3marketingdb_domain_model_partner',
'foreign_table_where' => 'ORDER BY tx_toco3marketingdb_domain_model_partner.partnerpkey',
'minitems' => 0,
'maxitems' => 20,
),
),
Model:
/**
* Partner
*
* #var Tx_Extbase_Persistence_ObjectStorage<Tx_Toco3Marketingdb_Domain_Model_Partner>
* #lazy
*/
protected $partner;
/**
* Sets the partner
*
* #param Tx_Extbase_Persistence_ObjectStorage<Tx_Toco3Marketingdb_Domain_Model_Partner> $partner
* #return void
*/
public function setPartner(Tx_Extbase_Persistence_ObjectStorage $partner) {
$this->partner = $partner;
}
Controller:
$partner = new Tx_Extbase_Persistence_ObjectStorage();
if (count($partnerarr) > 0){
foreach($partnerarr as $p){
$partner->attach( $this->partnerRepository->findByUid($p));
}
}
$organisation = $this->organisationRepository->findByUid($uid)
$organisation->setPartner($partner);
This is working as long there is an Object in the ObjectStorage. So I can add/delete/change relations. But when $partnerarr is empty an no objects get attached an empty Tx_Extbase_Persistence_ObjectStorage is assigned, the old values do not get "deleted". I also tried to assign null or "" but the an error occures because an ObjectStorage is needed. If I assign the empty ObjectStorage I don't get an error, but the old values still maintain :(
Any idea?
Thank you
Christian
Call the detach or removeAll methods to remove certain or all objects of the storage.
/** #var \Tx_Extbase_Persistence_ObjectStorage $organisationPartners */
$organisationPartners = $organisation->getPartner();
foreach ($organisationPartners as $partner) {
$organisationPartners->detach($partner);
}
Thank you #Wolfgang for your message.
I added the following function to my model:
/**
* detach Partner
*
* #param Tx_Toco3Marketingdb_Domain_Model_Partner $partner
* #return void
*/
public function detachPartner($partner) {
$this->partner->detach($partner);
}
In the controller I added:
$persistanceManager = t3lib_div::makeInstance('Tx_Extbase_Persistence_Manager');
$organisation = $this->firmaRepository->findByUid($uid);
$organisationPartners = $organisation->getPartner();
foreach ($organisationPartners as $organisationPartner) {
$organisation->detachPartner($organisationPartner);
}
$persistanceManager->persistAll();
$organisation->setPartner($partner);
It is important to persist before setting the new (empty) value...

Symfony 3 | Custom constraints on CollectionType entities

I followed the documentation and was able to add custom constraints on many of my fields (http://symfony.com/doc/current/validation/custom_constraint.html).
I'm figuring a problem with CollectionType field.
My custom constraint just check if user didn't tap multiple space in field (the constraint doesn't matter anyway).
I have a Question form with a title and answers :
$builder
->add('title', TextType::class)
->add('answers', CollectionType::class, array(
'entry_type' => AnswerType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
))
I have my constraint :
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class ContainsText extends Constraint
{
public $message = 'constraint_error';
}
And my constraint validator :
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsTextValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// It checks if user didn't had multiple space in field
if (strlen(trim($value)) == 0) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
In my entities :
Question:
use XX\XXBundle\Validator\Constraints as CustomAssert;
class Question
{
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, unique=true)
* #CustomAssert\ContainsText
*/
private $title;
...
}
Answer :
use XX\XXBundle\Validator\Constraints as CustomAssert;
class Answer
{
/**
* #var string
*
* #ORM\Column(name="text", type="string", length=255, unique=true)
* #CustomAssert\ContainsText
*/
private $text;
...
}
In my form validation, if in Question title I tap many spaces, I get a form validation error with my "constraint_error" message => Everything is working.
But, if in Question answers text I tap many spaces, the form validation doesn't return any errors and my question is created with empty answers !
It seems that, if the field comes from a CollectionType, the custom asserts are ignored.
What I don't understand is, if i had a Assert (like #Assert\Blank(), not a custom one) on answer text, even if we are in a CollectionType, the assert is not ignored and I can't validate a form with a blank answer.
What did I miss here ? TY
Not sure which Symfony 2 version you use, but depending if that is pre 2.8 or later you have different ways to tackle this:
v2.8+ and v3.0+
Starting with v2.8, which I suspect you could be using given AnswerType::class, cascade_validation was deprecated. Instead, you need to applty Valid constraint, on you Question::$answers class member. Something like this:
class Question
{
/**
* ... Other anotaions go here
*
* #Assert\Valid()
*/
private $answers
}
Pre v2.8:
You need to specify cascade_validation option:
$builder
->add('title', TextType::class)
->add('answers', CollectionType::class, array(
'entry_type' => AnswerType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'cascade_validation' => true // <========= THIS
));
Hope this helps...

TYPO3 Extbase: InversedBy 1:1 Relation

Is it possible to inverse a 1:1 Relation without adding a second field into DB in Extbase?
Example:
The extension has Contact-Persons which can have an fe_user.
The Contact-Person-Domain-Model is the owning site of the relation.
Now you can use $contactPerson->getFrontendUser();
Is there any way to add an inversed property to FrontendUser without adding it to the DB?
So you can use $frontendUser->getContactPerson(),
or even more important: $frontendUserRepository->findByContactPerson();
I tried adding the property to the FrontendUser-Model:
/**
* FrontendUser
*/
class FrontendUser extends \TYPO3\CMS\Extbase\Domain\Model\FrontendUser
{
/**
* #var \Vendor\ExtKey\Domain\Model\ContactPerson
*/
protected $contactPerson = null;
}
And overriding the fe_users TCA:
$GLOBALS['TCA']['fe_users']['columns']['contact_person'] = array(
'exclude' => 1,
'label' => 'LLL:EXT:ExtKey/Resources/Private/Language/locallang_db.xlf:tx_ExtKey_domain_model_contactperson',
'config' => array(
'type' => 'inline',
'foreign_table' => 'tx_ExtKey_domain_model_contactperson',
'foreign_field' => 'frontend_user',
'minitems' => 0,
'maxitems' => 1,
),
);
But when I call:
/**
* The repository for Customers
*/
class FrontendUserRepository extends \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
{
/**
* #param \Vendor\ExtKey\Domain\Model\ContactPerson $contactPerson
* #param boolean $ignoreEnableFields
* #param boolean $respectStoragePage
* #return object
*/
public function findByContactPerson(ContactPerson $contactPerson, $ignoreEnableFields = false, $respectStoragePage = true){
$query = $this->createQuery();
$query->getQuerySettings()
->setIgnoreEnableFields($ignoreEnableFields)
->setRespectStoragePage($respectStoragePage);
$query->matching($query->equals('contactPerson', $contactPerson));
return $query->execute()->getFirst();
}
}
It creates following SQL-Error:
Unknown column 'fe_users.contact_person' in 'where clause'
Computed bidirectional 1:1 relations are not supported in TYPO3, only m:n relations support that with an extra definition in TCA - which also requires an additional field on the opposite site of the relation.
Concerning you scenario, you have to create the additional property and database field in an extended FrontendUser domain model on your own.
There is a possibility like described here: http://www.oliver-weiss.com/blog/einzelansicht/article/relationen-in-typo3-teil-1-11-relation/News/detail/ . Additionally, instead of having an "inline" relation on both sides, this also works if only one side is "inline" and the other side is "select". But the single (inverted) "inline" relation needs to have "foreign_field" defined. Following this, the "foreign" uid is only stored on one side of the relation.

Using FAL in Extbase correctly

Domain model
class Image extends AbstractContent {
/**
* #var \TYPO3\CMS\Extbase\Domain\Model\FileReference
*/
protected $file;
/**
* Gets the image file
*
* #return \TYPO3\CMS\Extbase\Domain\Model\FileReference
*/
public function getFile() {
return $this->file;
}
/**
* Sets the image file
*
* #param \TYPO3\CMS\Extbase\Domain\Model\FileReference $file
* #return void
*/
public function setFile($file) {
$this->file = $file;
}
}
Import service fragments
/**
* #var \TYPO3\CMS\Core\Resource\ResourceStorage
*/
protected $defaultStorage;
[...]
$this->defaultStorage = ResourceFactory::getInstance()->getDefaultStorage();
[...]
$file = $this->defaultStorage->addFile(
'/tmp/4711',
$this->defaultStorage->getRootLevelFolder(),
'foo.jpg',
'overrideExistingFile'
);
$falReference = ResourceFactory::getInstance()->createFileReferenceObject(
array(
'uid_local' => $file->getUid(),
'uid_foreign' => uniqid('NEW_'),
'uid' => uniqid('NEW_'),
)
);
$reference = GeneralUtility::makeInstance(FileReference::class);
$reference->setOriginalResource($falReference);
$content = GeneralUtility::makeInstance(Image::class);
$content->setFile($reference);
After saving $content the image is available through the record and the filemount but the Ref column in BE > FILE > File List) is - and not >= 1. So its look like the reference is some how broken. When I'm using the BE to add an image to the record it's all fine. I'm using TYPO3 CMS 7.3-dev.
What's wrong with my code?
I get the hint in the Slack channel of TYPO3.
You just need to set plugin.tx_myext.persistence.updateReferenceIndex = 1 respectively module.tx_myext.persistence.updateReferenceIndex = 1 and the index will be updated.
Alternatively you could use \TYPO3\CMS\Core\Database\ReferenceIndex::updateRefIndexTable().
When I had to use FAL in my extension I found this link:
http://t3-developer.com/extbase-fluid/extensions-erweitern/fal-in-eigenen-extensions/fal-in-typo3-extensions-verwenden/
Since it is in German, I will in the very shortest explain what is done there:
extend your data model in ext_tables.sql
add a column of some char type (e.g. varchar)
add your column to the column section in your TCA array in ext_tables.php
'mypictures' => array(
'exclude' => 1,
'label' => 'My Pictures',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig('image', array(
'appearance' => array(
'createNewRelationLinkTitle' => 'LLL:EXT:cms/locallang_ttc.xlf:images.addFileReference'
),
'minitems' => 0,
'maxitems' => 99,
), $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']),
),
Extend your modelfiles. Pay attention to annotations!
You can use your media in your fluid template

TYPO3 Extbase: How to sort child objects

I have an Extbase Model Article and a 1:n Relation Product. In Article TCA i have an inline field configuered. In my Article Template I want to display all related Products. These are oredered by uid. How can i change the ordering of the child objects to the field sorting to be able to manually sort them. ( In the backend form the sorting is possible, only diplaying them sorted by field sorting is not possible )
thanks,
Lukas
The easiest way to sort child elements in a object storage is to manipulate the TCA.
Example:
$TCA['tx_myext_domain_model_ordering'] = array(
...
'columns' => array(
'services' => array(
'exclude' => 0,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xml:tx_myext_domain_model_ordering.services',
'config' => array(
'type' => 'inline',
'foreign_table' => 'tx_myext_domain_model_service',
'foreign_field' => 'ordering',
'foreign_sortby' => 'sorting',
'maxitems' => 9999,
'appearance' => array(
'collapseAll' => 0,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => 1,
'showPossibleLocalizationRecords' => 1,
'showAllLocalizationLink' => 1
),
),
),
),
...
);
With the filed 'foreign_sortby' => 'sorting', you can easily say on which field the childs should be ordered.
Using the dot notation you can sort by properties of subobjects:
$query->setOrderings(array(
'sorting' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
'subobject.sorting' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
));
edit: Caught me off guard, I think I read your question wrong. Yes, this sorts by child objet, but it applies that to the parent object, of course. If you want to sort the child objects themselves, you have to set that sorting in the repository of the child object, as #Urs explained.
Here's how I do it, in the repository class:
class ItemRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {
/**
* http://www.typo3.net/forum/thematik/zeige/thema/114160/?show=1
* Returns items of this repository, sorted by sorting
*
* #return array An array of objects, empty if no objects found
* #api
*/
public function findAll() {
$orderings = array(
'sorting' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
);
$query = $this->createQuery();
$query->setOrderings($orderings);
$query->getQuerySettings()->setRespectSysLanguage(FALSE);
$query->getQuerySettings()->setSysLanguageUid(0);
$result = $query->execute();
return $result;
}
}
I dont't really like the idea of doing basic sorting inside the view (mvc).
The Downside of most Database abstraction Layers ist, that you are quite often restricted to either mixup mvc or have a (sometimes slightly) lower performance.
I am at the same point right now. I am thinking about retrieving the parents (with their children attached). Then go into every single parent and get children for this parent in a sorted way.
public function findAllSorted() {
$query = $this->createQuery();
/* sort parents */
$query->setOrderings(array('name' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING));
$parents = $query->execute();
foreach($parents as $parent){
/* get children for every parent */
$children = $this->getChildrenSorted($parent);
// add children to parent TBD
}
/* Debug output */
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($parents);
}
public function getChildrenSorted(\mynamespace\ $parent) {
/* Generate an object manager and use dependency injection to retrieve children repository */
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('\TYPO3\CMS\Extbase\Object\ObjectManager');
$childrenRepository= $objectManager->get('MYNAME\Extname\Domain\Repository\Repository');
$children = $versionRepository->findSortedByName($parent);
return $children;
}
The function findSortedByName inside the children repos is using
$query->matching($query->equals('parent', $parent));
to retrieve only children of this parent and
$query->setOrderings(array('name' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING));
to be able to give them back in an ordered way.
Warning: Depending on the size and quantity of all retrieved objects, this way might arise a huge performance issue.