EasyAdmin 3 Symfony 5 CrudController AssociationField - crud

I'm using EasyAmdin 3 with Symfony 5 and I have a OneToMany relation between Challenge and Encadrement. Defined In Challenge.php:
/**
* #ORM\OneToMany(targetEntity=Encadrement::class, mappedBy="challengePartner")
*/
private $bornes;
I made a CRUDController for Challenge, and I want to be able to add Encadrement directly when creating/editing a Challenge. I did this :
AssociationField::new('bornes'),
I can choose between all the Encadrement already created. But what I want is to be able to multiple add Encadrement and I can't find how to do this. I tried making my own EncadrementType :
class EncadrementType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
"label" => "Nom"
))
->add('low', IntegerType::class, array(
"label" => "Borne basse"
))
->add('high', IntegerType::class, array(
"label" => "Borne haute"
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Encadrement::class,
]);
}
}
And something like this in the ChallengeCRUD:
AssociationField::new('bornes')->setFormType(EncadrementType::class),
But I get this error when I don't even use those options:
An error has occurred resolving the options of the form "App\Form\EncadrementType": The options "class", "multiple", "query_builder" do not exist. Defined options are: "action", "allow_extra_fields", "allow_file_upload", "attr", "attr_translation_parameters", "auto_initialize", "block_name", "block_prefix", "by_reference", "compound", "constraints", "csrf_field_name", "csrf_message", "csrf_protection", "csrf_token_id", "csrf_token_manager", "data", "data_class", "disabled", "ea_crud_form", "empty_data", "error_bubbling", "error_mapping", "extra_fields_message", "getter", "help", "help_attr", "help_html", "help_translation_parameters", "inherit_data", "invalid_message", "invalid_message_parameters", "is_empty_callback", "label", "label_attr", "label_format", "label_html", "label_translation_parameters", "legacy_error_messages", "mapped", "method", "post_max_size_message", "property_path", "required", "row_attr", "setter", "translation_domain", "trim", "upload_max_size_message", "validation_groups".
I tried adding the multiple option to the AssociationField but it does nothing:
AssociationField::new('bornes')->setFormTypeOption("multiple","true"),
I'm stuck there, thanks for any help !

Try CollectionField like this:
<?php
yield CollectionField::new('bornes')
->setFormTypeOptions([
'delete_empty' => true,
'by_reference' => false,
])
->setEntryIsComplex(false)
->setCustomOptions([
'allowAdd' => true,
'allowDelete' => true,
'entryType' => EncadrementType::class,
'showEntryLabel' => false,
])
;

Related

Magento2 module fields still visible in admin after module is disabled

I am experiencing an issue while trying to disable a Magento2 module causing custom values to still be visible in the customer edit page.
I would like to know what i have to do to completely get rid of a module and its data from a Magento2 system.
Magento version: 2.3.2
PHP version: 7.2.19
A custom(own) Magento2 module was installed by:
Copying code: app/code/VENDOR/MODULE
Running: magento module:enable VENDOR_MODULE
Running magento setup:upgrade
This module creates a couple of Customer EAV attributes that correctly show in the customer edit form.
I am able to populate/save/update values successfully.
I am disabling the module as such:
Running: magento module:disable VENDOR_MODULE
Running magento setup:upgrade
Completely removing the app/code/VENDOR/MODULE directory
When i navigate back to the customer edit page i can still see the attributes, visible and populated with previously entered data.
At this point i have tried the following:
Manually removing the entry in setup_module.
Including a Uninstall class.
A combination of magento cache:clean && magento setup:di:compile.
Classes attached:
InstallData.php
namespace VENDOR\MODULE\Setup;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Customer\Model\Customer;
use Magento\Customer\Setup\CustomerSetupFactory;
class InstallData implements InstallDataInterface {
private $customerSetupFactory;
/**
* Constructor
*
* #param \Magento\Customer\Setup\CustomerSetupFactory $customerSetupFactory
*/
public function __construct(CustomerSetupFactory $customerSetupFactory) {
$this->customerSetupFactory = $customerSetupFactory;
}
/**
* {#inheritdoc}
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$this->installModule1($setup, $context);
$this->installModule2($setup, $context);
}
private function installModule1(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'module1', [
'type' => 'varchar',
'label' => 'Module1 label',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 500,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute('customer', 'module1')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]
]);
$attribute->save();
}
private function installModule1(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'module2', [
'type' => 'varchar',
'label' => 'Module2 label',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 500,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute('customer', 'module2')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]
]);
$attribute->save();
}
}
Uninstall.php
namespace VENDOR\MODULE\Setup;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Db\Select;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UninstallInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class Uninstall implements UninstallInterface {
private $_eavSetupFactory;
private $_mDSetup;
public function __construct(EavSetupFactory $eavSetupFactory, ModuleDataSetupInterface $mDSetup) {
$this->_eavSetupFactory = $eavSetupFactory;
$this->_mDSetup = $mDSetup;
}
public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context) {
$installer = $setup;
$eavSetup = $this->_eavSetupFactory->create(['setup' => $this->_mDSetup]);
$eavSetup->removeAttribute(\Magento\Catalog\Model\Customer::ENTITY, 'module1');
$eavSetup->removeAttribute(\Magento\Catalog\Model\Customer::ENTITY, 'module2');
}
}
For Customer attributes you need to delete the specific attributes entry from table "eav_attribute" you can search by "attribute_code" and delete that row, you have to delete attributes from the database because there is no functionality in admin to delete an attribute
This is the method by which the custom attribute can be removed as I also tried manually to delete that module attribute and it took more than 1 day to find this solution.
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->removeAttribute(Customer::ENTITY, "<attribute name>");
}
You can remove multiple attribute too at same time by separating attribute name by commas.
After this just run -
bin/magento setup:upgrade && bin/magento setup:static-content:deploy -f

Symfony 3.3 CraueFormFlowBundle Request_stack is empty

my first question to this site is a little difficult to describe.
I am quite new to Symfony, startet with 3.2 and updated recently to 3.3.5 (not sure if relevant for the problem).
I tried to use CraueFormFlowBundle (multistep form bundle) but cannot get it to work.
The problem is that trying to access the flow results in an exception:
Error: Call to a member function getCurrentRequest() on null
Symfony\Component\Debug\Exception\ FatalErrorException
in vendor/craue/formflow-bundle/Form/FormFlow.php (line 191)
Line 191 shows: $currentRequest = $this->requestStack->getCurrentRequest();
Modifying the FormFlow.php with dump line shows that $this->requestStack is null.
I have not enough knowledge about this bundle to know where to start looking for the problem.
The flow definition is based on the location example:
namespace EngineeringBundle\Form;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
class SelectExaminationFlow extends FormFlow
{
/**
* {#inheritDoc}
*/
protected function loadStepsConfig()
{
dump("loadStepsConfig");
return array(
array(
'label' => 'engineering.discipline',
'form_type' => new SelectExaminationStep1Form(),
),
array(
'label' => 'engineering.date',
'form_type' => new SelectExaminationStep2Form(),
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveRegion();
},
),
array(
'label' => 'confirmation',
),
);
}
The form definition is also quite simple and works without problems:
class SelectExaminationStep1Form extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump("buildForm");
$builder
->add('id', HiddenType::class)
->add('discipline', EntityType::class, array(
'class' => 'EngineeringBundle:Discipline',
'choice_label' => 'disciplineName',
'label' => 'engineering.discipline.label'
)
);
}
public function getName() {
return $this->getBlockPrefix();
}
public function getBlockPrefix() {
return 'createEngineeringStep1';
}
}
services.yml:
EngineeringBundle\Form\SelectExaminationFlow:
parent: craue.form.flow
autowire: false
autoconfigure: false
public: true
engineering.form_flow:
alias: EngineeringBundle\Form\SelectExaminationFlow
public: true
Controller:
/**
* #Route("create", name="engineering_create")
*/
public function createAction()
{
return $this->processFlow(new ExaminationDate(), $this->get('engineering.form_flow'));
}
Thanks in advance
Sebastian
I was having the same problem, resolved it by adding a constructor to vendor/craue/formflow-bundle/Form/FormFlow.php with the following content:
public function __construct(RequestStack $requestStack, FormFactoryInterface $formFactory, DataManagerInterface $dataManager, EventDispatcherInterface $eventDispatcher) {
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
$this->dataManager = $dataManager;
$this->eventDispatcher = $eventDispatcher;
}
Make sure to place it after all setter-methods. Problem seems to be related to a symfony update.

Access control of a Module in Yii2

I am having a trouble with login part.
I read this topic : http://www.yiiframework.com/wiki/771/rbac-super-simple-with-admin-and-user/ .
Then i follow its steps, but in step 6. it only configs for just one Controller. I have a Module called Admin with many controllers in it and i don't know how to apply this access control to the whole module. Can anyone help me ?
Sorry for my bad English.
You can create AdminController class, which will extends yii\web\Controller where you define your access rules in behaviors method and make other module controllers extend your AdminController and override behaviors method like this:
public function behaviors()
{
return \yii\helpers\ArrayHelper::merge(parent::behaviors(), [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
]);
}
Here parent::behaviors() are behaviors from AdminController which define default access rules, and you merge them with specific behaviors in your child controller. It gives you flexibility to override some access rules if you need.
I can propose a variation of the method from the article that you mentioned.
Make first 2 steps as it was described and then do the following:
1. Add the field role to User model and evaluate it with thevalue of one of the constants from the article's example (User::ROLE_ADMIN or User::ROLE_USER)
2. Override the yii\web\User->can()
public function can($permissionName, $params = [], $allowCaching = true)
{
/** #var \app\models\User $user */
$user = $this->identity;
$access = false;
do {
if (\Yii::$app->user->isGuest) {
break;
}
if ($user->role === \common\models\User::ROLE_ADMIN) {
$access = true;
break;
}
if (is_array($permissionName)) {
$access = in_array($user->role, $permissionName);
} else {
$access = $permissionName === $user->role;
}
} while (false);
return $access;
}
So now you can check user's role like this:
\Yii::$app->user->can(User::ROLE_USER)
3. You say:
i don't know how to apply this access control to the whole module.
Then open your module class and add the following to the behaviors() method:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => [User::ROLE_ADMIN]
]
]
]
];
}
In this example we grant access to ROLE_ADMIN to all actions of all controllers of your module.
That's it.
Make a custom model AccessRules.php as shown below:
<?php
namespace app\models;
class AccessRules extends \yii\filters\AccessRule
{
/**
* #inheritdoc
*/
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if ($role === '?') {
if ($user->getIsGuest()) {
return true;
}
} elseif ($role === '#') {
if (!$user->getIsGuest()) {
return true;
}
// Check if the user is logged in, and the roles match
} elseif (!$user->getIsGuest() && (int)$role === $user->identity->user_role) {
return true;
}
}
return false;
}
}
?>
Now open your site controller and add the following code in fuction behavior part:
use app\models\AccessRules;
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
// We will override the default rule config with the new AccessRule class
'ruleConfig' => [
'class' => AccessRules::className(),
],
'only' => ['create', 'update', 'delete','index'],
'rules' => [
[
'actions' => ['create', 'update', 'delete','index'],
'allow' => true,
// Allow admin to create
'roles' => [
'1'
],
]
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
],
],
];
}
According to The Yii2 Guide
"ACF is an action filter that can be used in a controller or a module" in the same way.
Just add below code in controller which you want to restrict functionality
'access' => [
'class' => AccessControl::className(),
'rules' =>
[
[
'actions' => ['index','view'],
'allow' => true,
'roles' => ['#']
],
[
'actions' => ['create','update','delete'],
'allow' => true,
'roles' => ['#'],
'matchCallback' => function ($rule, $action)
{
return Admin::isUserAdmin(Yii::$app->user->identity->username);
}
],
],
],

FOSRestBundle post many to one relation

I would like to know how to properly post data when Entity has another ManyToOne relation in FOSRestBundle.
User entity has locale (locale_id):
/**
* #ORM\ManyToOne(targetEntity="Locale")
* #ORM\JoinColumn(name="locale_id", referencedColumnName="id")
*/
private $locale;
I was hoping that passing something like:
{
"user":{
"firstName":"John",
"emailAddress":"somewhere#somehow.com",
"lastName":"Doe",
"sex":"1",
"locale":{
"id":"1"
}
}
}
will work, but it does not pass the validation and Symfony throws:
{"code":400,"message":"Validation Failed","errors":{"children":{"firstName":[],"lastName":[],"emailAddress":[],"sex":[],"locale":{"errors":["This value is not valid."]}}}}
As you can see, locale is still wrong.
Does anyone know how can I post it properly?
EDIT
Here is how the form looks like:
<?php
namespace Software\Bundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Class UserType
* #package Software\Bundle\Form\Type
*/
class UserType extends AbstractFormType
{
public function buildForm(FormBuilderInterface $builder, array $option)
{
$builder
->add('firstName', 'text', [
'label' => 'word.first_name',
'required' => true
])
->add('lastName', 'text', [
'label' => 'word.last_name',
'required' => true
])
->add('emailAddress', 'email', [
'label' => 'word.email_address',
'required' => true
])
->add('sex', 'choice', [
'label' => 'word.sex',
'choices' => [
'0' => 'word.male',
'1' => 'word.female'
],
'required' => true,
'empty_value' => 'word.select',
'empty_data' => null
])
->add('locale', 'entity', [
'label' => 'word.locale',
'required' => false,
'property' => 'code',
'class' => 'SoftwareBundle:Locale',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('l')
->orderBy('l.code', 'ASC');
},
'placeholder' => 'word.select',
'empty_data' => null
])
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'translation_domain' => 'general',
'data_class' => 'Software\Bundle\Entity\User',
'attr' => ['novalidate' => 'novalidate'],
'csrf_protection' => false
]);
}
public function getName()
{
return 'user';
}
}
EDIT 2
And the controller:
public function postAction(Request $request)
{
$form = $this->createForm(new UserType(), new User());
$form->handleRequest($request);
if($form->isValid())
{
die('are you valid or not??');
}
return $this->view($form, 400);
}
Try without the "1" and only with 1 , otherwise it can be interpreted as string.
Edit :
{
"user":{
"firstName":"John",
"emailAddress":"somewhere#somehow.com",
"lastName":"Doe",
"sex":"1",
"locale": 1
}
}
}

Symfony Forms Neither the property "Category" nor one of the methods "getCategory()" error

I keep getting the following error:
Neither the property "Category" nor one of the methods
"getCategory()", "category()", "isCategory()", "hasCategory()",
"__get()" exist and have public access in class
"Test\TesterBundle\Model\Products"
I've got 3 tables: Products, Category and ProductCategories. The ProductCategories is the ID of the Producs and Category Table (Many to Many). But I keep getting the error above.
I've got the following form builders:
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('ProductName');
$builder->add('ProductDescription', 'textarea', array("required"=> false));
$builder->add('ShortDescription', null);
$builder->add('SKU', null);
$builder->add('UnitWeight', null);
$builder->add('UnitPrice', null);
$builder->add('UnitLength', null);
$builder->add('UnitHeight', null);
$builder->add('UnitDepth', null);
$builder->add('URL', null);
$builder->add('MetaTitle', null);
$builder->add('MetaDescription', null);
$builder->add('MetaKeywords', null);
$builder->add('Category', 'collection', array(
'type' => new \Test\TesterBundle\Form\Type\CategoryType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'validation_groups' => array('newProducts'),
'data_class' => 'Test\TesterBundle\Model\Products',
));
}
public function getName(){
return "Products";
}
}
And The second being:
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('CategoryName', 'model', array(
'class' => 'Test\TesterBundle\Model\Productcategory',
'required' => false,
'multiple' => true,
'expanded' => false,
'property' => 'label'
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Test\TesterBundle\Model\Productcategory'
));
}
public function getName(){
return "ProductCategory";
}
}
Any thoughts on what would be causing the issue? I'm not sure if it's the instance that is used in the controller or if it is because I've setup the form builders incorrectly and cannot get a direct relation to the Category field (which it won't). If I use mapped => false, then it works however the builder has to populate the select box from the database, which settings mapped to false stops it doing this.