TYPO3 Extbase Dependency Injection Error/Bug in v7? - typo3

I am currently upgrading an extbase-extension to be TYPO3 v7 compatible,
and there is a very strange extbase behavior I simply have no clue to.
Within BackendController, A derived model has to be updated,
which looks like this:
/**
* action update
*
* #param \Vendor\MyExt\Domain\Model\Thing $thing
* #return void
*/
public function updateAction(\Vendor\MyExt\Domain\Model\Thing $thing) {
if ($this->request->hasArgument('exit')) {
$this->redirect('list');
exit;
}
$this->setFalItems($thing);
$this->updateStuff($thing);
$this->updateTypeModel($thing);
//...
}
protected function updateTypeModel( \Vendor\MyExt\Domain\Model\Thing $thing ) {
//...
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
$mytypeRepository = $this->objectManager->get('Vendor\MyExt\Domain\Repository\TypeWhateverRepository');
$typeModel = $mytypeRepository->findByUid( $TypeId );
//...
in v6, vardump( $typemodel ) showed the corresponding Object,Vendor\MyExt\Domain\Model\TypeWhatever
in v7, vardump( $typemodel ) is showing the parent Object,Vendor\MyExt\Domain\Model\Thing
Why is it working in v6?
Why is the exact same code not working in v7 anymore?
[dreams of dreaded bugs at night]
I digged a little bit deeper, This problem is somehow related to Dependency Injection.:
/**
* typeWhateverRepository
*
* #var \Vendor\MyExt\Domain\Repository\TypeWhateverRepository
* #inject
*/
protected $typeWhateverRepository;
protected function updateTypeModel(\Vendor\MyExt\Domain\Model\Thing $thing) {
// $typeWhateverRepository = $this->objectManager->get('Vendor\\MyExt\\Domain\\Repository\\TypeWhateverRepository');
$typeModel = $this->typeWhateverRepository->findByUid($thing->getTypeId());
-> still the same problem,
-> Call to undefined method Vendor\MyExt\Domain\Model\Thing::setWhatever()
So, DI didn't work at all, Grmpf.
What other prerequisites are necessary to get the DI right?
(BTW, inbetween tests, i un-and reinstall the ext, clearing all caches via installtool.)
Thank you in advance.

First of all... lets do some clean up...
I would recommend to use the inject of your repository:
/**
* seminarRepository
*
* #var \Vendor\MyExt\Domain\Repository\TypeWhateverRepository
*/
protected $typeWhateverRepository;
/**
* #param \Vendor\MyExt\Domain\Repository\TypeWhateverRepository $typeWhateverRepository
*/
public function injectTypeWhateverRepository(TypeWhateverRepository $typeWhateverRepository)
{
$this->typeWhateverRepository= $typeWhateverRepository;
}
Then I would use an Relation from Thing to Type so you don't have to fetch these from your Repository:
/**
* #lazy
* #var \Vendor\MyExt\Domain\Model\TypeWhatever
*/
protected $typeWhatever = null;
/**
* #return \Vendor\MyExt\Domain\Model\TypeWhatever $typeWhatever
*/
public function getTypeWhatever()
{
return $this->typeWhatever;
}
/**
* #param \Vendor\MyExt\Domain\Model\TypeWhatever $typeWhatever
*
* #return void
*/
public function setTypeWhatever(TypeWhatever $typeWhatever)
{
$this->typeWhatever = $typeWhatever;
}
In your Thing TCA put than:
'type_whatever' => [
'exclude' => 0,
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_thing.type_whatever',
'config' => [
'type' => 'select',
'foreign_table' => 'tx_myext_domain_model_typewhatever',
'items' => [
['LLL:EXT:my_ext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_thing.choose', 0],
],
'minitems' => 1,
'maxitems' => 1,
],
],

The Solution to this is trivial, but was hard to find, since I was doing an Extension Update.
the extbase-typoscript setup was missing the subclasses definition m)
extbase setup is usually found in the filetypo3conf/ext/my_ext/Configuration/TypoScript/setup.txt:
config.tx_extbase.persistence.classes {
Vendor\MyExt\Domain\Model\Thing {
subclasses {
0 = Vendor\MyExt\Domain\Model\TypeWhatever
}
}
}
Also note that it is necessary for the class to have a proper 'extends' definition in the Model file.
I still wonder why it worked in v6 at all - but well, nevermind.

Related

Exception while property mapping at property path "":Property"propertyname"was not found in target object of type"In2code\Femanager\Domain\Model\User"

I have implemented the below given code to extend the femanager extension in my own custom extension.
classes.php:
return [
\In2code\Femanager\Domain\Model\User::class => [
'subclasses' => [
\TYP\TypCfg\Domain\Model\FrontendUser::class
]
],
\TYP\TypCfg\Domain\Model\FrontendUser::class => [
'tableName' => 'fe_users',
'recordType' => 0,
]
];
ext_localconf.php:
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
->registerImplementation(NewController::class, \TYP\TypCfg\Controller\NewController::class);
FrontendUser.php:
<?php
namespace TYP\TypCfg\Domain\Model;
/**
* Class FrontendUser
* #package TYP\TypCfg\Domain\Model
*/
class FrontendUser extends \In2code\Femanager\Domain\Model\User
{
const TABLE_NAME = 'fe_users';
/**
* #var string
*/
protected $propertyname = '';
public function getPropertyname()
{
return $this->propertyname;
}
/**
* #param string $propertyname
*/
public function setPropertyname($propertyname)
{
$this->propertyname = $propertyname;
}
}
All the steps given in the link https://docs.typo3.org/p/in2code/femanager/main/en-us/Features/NewFields/Index.html are performed properly. But still I am facing this error. How can I solve this?
If you are using v7.1 of femanager in TYPO3 11 or 12 you have to add this to your extensions /Configuration/Services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
In2code\Femanager\Domain\Model\User: '#TYP\TypCfg\Domain\Model\FrontendUser'

TYPO3 TCA selectCheckBox not working with extension femanager

I've created a selectCheckBox in TYPO3 like this:
'region' => [
'exclude' => true,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_catalog.region',
'config' => [
'type' => 'select',
'renderType' => 'selectCheckBox',
'items' => [
['LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_catalog.region.1', 1],
['LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_catalog.region.2', 2],
['LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_catalog.region.3', 3],
['LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_catalog.region.4', 4],
],
],
],
When I save it, the database has the comma separated IDs as value like:
1,3,4
Now I want to get the value in frontend via the femanager extension.
I've created this configuration for it:
/**
* region
*
* #var array
*/
protected $region = [];
/**
* Returns the region
*
* #return array $region
*/
public function getRegion()
{
return $this->region;
}
/**
* Sets the region
*
* #param array $region
* #return void
*/
public function setRegion($region)
{
$this->region = $region;
}
Database Configuration:
region varchar(255) DEFAULT '' NOT NULL,
But when I try to debug the output like this:
<f:debug>{catalog.region}</f:debug>
It just says this:
array(empty)
So my getRegion function isn't working. Can somebody please give me a hint why it isn't getting the values.
$region is declared to be an array and your setter setRegion() is expecting an array as parameter. But the stored value in the database is a varchar. So, the mapping of the database record on your model will fail for this property.
This is working with your TCA:
/**
* #var string
*/
protected string $region = '';
/**
* #return array
*/
public function getRegion(): array
{
return GeneralUtility::intExplode(',', $this->region, true);
}
/**
* #param mixed $region
*/
public function setRegion($region): void
{
$this->region = (is_array($region) ? implode(',', array_filter($region, 'is_int')) : $region);
}

Symfony ValidatorComponent > AnnotationMapping in FormComponent

Im working on a project where I'm using some Symfony Components. My problem is how to make the Form Component's validation of Forms use AnnotationMapping to find the constraints.
SetUp:
global $loader; //composer - autoload
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
$formFactory = Forms::createFormFactoryBuilder()
[...]
->addExtension(new ValidatorExtension($validator))
->getFormFactory();
Entity
/**
* #ORM\Entity
* #ORM\Table(name="..")
*/
class Conductor extends AbstractEntity {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #Assert\NotBlank()
* #ORM\Column(type="string")
*/
protected $pattern;
[...]
}
Building the Form
$builder = $App->getFormFactory()->createBuilder(FormType::class, $entity_data);
foreach ($fields as $field) {
$builder->add(
$field,
null,
[
"attr" => array("class" => "..."),
]
);
}
$builder->getForm();
FormSubmit / Validation
if($request->isMethod('POST')) {
$formTable = $this->createFormTable( array() );
$form = $formTable->buildForm($entity);
$form->submit($this->dataMapper->formDataFromPost());
/*
$entity = $this->dataMapper->mapFromPost();
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
*/
if($form->isValid()) {
[...]
} else {
[...]
}
}
Im trying to make the NotBlank() Constraints work. But my form passes the validation in any case. If I use a new validator and validate with it, it will show me the correct Errors. But the Form->isValid() function does not. Maybe it is not configured correctly to use AnnotationMapping? Thank you very much in advance for tipps or solutions!
Problem localization
The form handleRequest / submit and validation are working as expected!
The form does not have any constraints!!
-> Mapping the Constraints from Annotation is not happening / working.
I did find a similar question: Why does Symfony form not validate my DTO with constraint annotations?
I wasn't able to find a solution to enable the mapping that should happen inside the FormComponent with the ValidatorExtension.
But I did find a functional workaround. My approach is to get the Constraints from the readPropertyMetadata function of the validator:
use Symfony\Component\Validator\Validation;
public function buildForm(AbstractEntity $entity) {
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
$fields = [*ENTITY PRPERTIES*];
$classMeta = $validator->getMetadataFor($entity);
foreach ($fields as $field) {
$metadata = $classMeta->getPropertyMetadata($field);
if(is_array($metadata) && count($metadata) > 0) {
$constraints = $classMeta->getPropertyMetadata($field)[0]->constraints;
} else {
$constraints = [];
}
$builder->add(
$field,
null,
[
"attr" => array("class" => "..."),
"constraints" => $constraints
]
);
}
}
As now the constraints are added to the form the validation finally works as expected.

Adding custom column to customer_entity

I am trying to add an custom column to customer_entity, which should be editable in customer form in backend.
I am able to add the column to the database table via an UpdateSchema Script in my Module.
But how can I populate it in the customer form and in the grid?
What I tried so far:
I added an attribute with the same name (=column name) with UpdateDataScript, $customerSetup->addAttribute()...
Customer_grid_flat is updated correctly on saving the user, but the value in the table customer_entity didn't get changed. It is saving its values inside the attribute table (customer_entity_varchar).
How can I set up the custom column correctly, so that its value is saved inside 'customer_entity' and not in 'customer_entity_varchar'?
Solution:
My custom attribute now gets properly saved inside customer_entity table.
UpgradeSchema.php
<?php
namespace Custom\MyModule\Setup;
use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
class UpgradeSchema implements UpgradeSchemaInterface
{
const CUSTOM_ATTRIBUTE_ID = 'custom_attribute';
/**
* #param SchemaSetupInterface $setup
* #param ModuleContextInterface $context
*/
public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
if (version_compare($context->getVersion(), '0.0.3', '<')) {
$setup->getConnection()->addColumn(
$setup->getTable('customer_entity'),
self::CUSTOM_ATTRIBUTE_ID,
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'nullable' => true,
'default' => null,
'comment' => 'Custom Attribute'
]
);
}
$setup->endSetup();
}
}
UpgradeData.php
<?php
namespace Custom\MyModule\Setup;
use Magento\Customer\Model\Customer;
use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\TestFramework\Helper\Eav;
class UpgradeData implements UpgradeDataInterface
{
/**
* #var CustomerSetupFactory
*/
private $customerSetupFactory;
/**
* #var IndexerRegistry
*/
protected $indexerRegistry;
/**
* #var \Magento\Eav\Model\Config
*/
protected $eavConfig;
/**
* #var \Magento\Eav\Model\Setup
*/
protected $eavSetupFactory;
/**
* #param CustomerSetupFactory $customerSetupFactory
* #param IndexerRegistry $indexerRegistry
* #param \Magento\Eav\Model\Config $eavConfig
*/
public function __construct(
CustomerSetupFactory $customerSetupFactory,
IndexerRegistry $indexerRegistry,
\Magento\Eav\Model\Config $eavConfig,
EavSetupFactory $eavSetupFactory
)
{
$this->customerSetupFactory = $customerSetupFactory;
$this->indexerRegistry = $indexerRegistry;
$this->eavConfig = $eavConfig;
$this->eavSetupFactory = $eavSetupFactory;
}
/**
* Upgrades data for a module
*
* #param ModuleDataSetupInterface $setup
* #param ModuleContextInterface $context
* #return void
*/
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$dbVersion = $context->getVersion();
if (version_compare($dbVersion, '0.0.3', '<')) {
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
$customerSetup->addAttribute(
'customer',
UpgradeSchema::CUSTOM_ATTRIBUTE_CODE,
[
'label' => 'Custom Attribute',
'required' => 0,
'visible' => 1, //<-- important, to display the attribute in customer edit
'input' => 'text',
'type' => 'static',
'system' => 0, // <-- important, to have the value be saved
'position' => 40,
'sort_order' => 40
]
);
/** #var EavSetupFactory $eavSetup */
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$typeId = $eavSetup->getEntityTypeId('customer');
$attribute = $eavSetup->getAttribute($typeId, UpgradeSchema::CUSTOM_ATTRIBUTE_ID);
$customerSetup->getSetup()->getConnection()->insertMultiple(
$customerSetup->getSetup()->getTable('customer_form_attribute'),
array('form_code' => 'adminhtml_customer', 'attribute_id' => $attribute['attribute_id'])
);
$setup->endSetup();
}
}
}

Checkbox field in symfony2

Good morning everyone! Is a form of.
Class ReleasesType:
$builder
->add('doid', 'text')
->add('dourl', 'text')
->add('artists', 'entity', array(
'class' => 'MReleaseCoreBundle:Artists',
'property' => 'name',
'expanded' => true ,
'multiple' => true
));
Сonnection with them one-to-many:
Class 'Artists':
/**
* #ORM\OneToMany(targetEntity="ReleasesArtists" , mappedBy="artists" , cascade={"all"})
* */
private $da;
public function __construct() {
$this->da = new \Doctrine\Common\Collections\ArrayCollection();
}
Class 'ReleasesArtists':
/**
* #ORM\ManyToOne(targetEntity="Releases", inversedBy="da")
* #ORM\JoinColumn(name="releases_id", referencedColumnName="id")
* */
private $releases;
/**
* #ORM\ManyToOne(targetEntity="Artists", inversedBy="da")
* #ORM\JoinColumn(name="artists_id", referencedColumnName="id")
* */
private $artists;
And of course the entity 'Releases':
/**
* #ORM\OneToMany(targetEntity="ReleasesArtists" , mappedBy="releases", cascade={"all"} , orphanRemoval=true)
*/
private $da;
public function getArtists() {
$artists = new ArrayCollection();
foreach($this->da as $p) {
$artists[] = $p->getArtists()->getName();
}
return $artists;
}
public function addDa($da) {
$this->da[] = $da;
}
public function setArtists($artists) {
foreach($artists as $p) {
$po = new \MRelease\CoreBundle\Entity\ReleasesArtists();
$po->setReleases($this);
$po->setArtists($p);
$this->addDa($po);
}
}
Connection is working correctly, all outputs. But does not "checked". In what may be the problem?
Thanks!
Into your controller, where you build and output your form, you have to do something like this
public function myFooAction(Request $request, $releasesId)
{
$repo = $this->getDoctrine()->getManager()->getRepository('YourBundleName:Releases');
$releasesObject = $repo->findOneById($releasesId);
$form = $this->createForm(new ReleasesType(), $releasesObject);
return $this->render('YourBundle::TemplateToRender, array('form'=>$form);
}
What happen here, and why is working?
I've made some assumptions as you don't provide any controller code. First of all, I assume that you have an action like myFooAction() where you do form operation and I suppose, also, that you pass to this action an id for load object from DB and tie it to your form - if I understood correctly your question.
So, first line of action is for retrieve repository for this object. Once you've got repo, you can fetch your object (second line). On third line I use Symfony2 form's facility and "connect" object to his form type: with this, all values contained into this object will be reported into your form (so checkboxes will have correct value). Last line is for render form.
Obviously, your action logic could be different but concept expressed here could be replicated with "different" implementation everywhere.