Im trying to get all validation constraints on an entity and translate theses constraints to Jquery validation rules, right now im able to get annotation defined constraints (thanks to : Symfony2 get validation constraints on an entity), but im having some trouble getting xml and yml ones.
$xml_file_loader = new XmlFileLoader("path_to_my_project/vendor/friendsofsymfony/user-bundle\FOS\UserBundle\Resources\config\validation.xml");
Using a similar code means that i need to know beforehand where the xml/yml file is located, i m trying to write somehow a generic code that can do this automatically.
Isn't there a way to get all constraints at once? if not how can i know the location of xml/yml files, and also in cases of inheritance i need to check for parent constraints... Is this doable?
private function getValidations()
{
$validations=[];
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new your_entity());
$constrainedProperties=$metadata->getConstrainedProperties();
foreach($constrainedProperties as $constrainedProperty)
{
$propertyMetadata=$metadata->getPropertyMetadata($constrainedProperty);
$constraints=$propertyMetadata[0]->constraints;
$outputConstraintsCollection=[];
foreach($constraints as $constraint)
{
$class = new \ReflectionObject($constraint);
$constraintName=$class->getShortName();
$constraintParameter=null;
switch ($constraintName)
{
case "NotBlank":
$param="notBlank";
break;
case "Type":
$param=$constraint->type;
break;
case "Length":
$param=$constraint->max;
break;
}
$outputConstraintsCollection[$constraintName]=$param;
}
$validations[$constrainedProperty]=$outputConstraintsCollection;
}
return $validations;
}
Returns:
array(13) (
[property1] => array(4) (
[NotBlank] => (string) notBlank
[NotNull] => (string) notBlank
[Type] => (string) string
[Length] => (int) 11
)
[property2] => array(4) (
[NotBlank] => (string) notBlank
[NotNull] => (string) notBlank
[Type] => (string) string
[Length] => (int) 40
)
..........
)
The returned array can be configured or used to define client side validation rules depending on the client-side validation library/code that you are using
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new yourentity());
The object $metadata now contains all the metadata about validations that concerns your specific entity.
Related
When i try to connect as a user (my user entity implement UserInterface), i always get this error:
Typed property Symfony\Component\Security\Core\Exception\AccountStatusException::$user must not be accessed before initialization
At: D:\cours\symfony\blog\vendor\symfony\security-core\Exception\AccountStatusException.php:45
So i implement *Serializable* like someone says it here: https://github.com/symfony/symfony/issues/38274#issuecomment-697231222 like this:
public function serialize(): array {
return ['id' => $this->getId(), 'email' => $this->getEmail(), 'password' => $this->getPassword(), 'roles' => $this->getRoles()];//FIXME ajouter rôle?
}
public function unserialize($serialized): void {
list($this->id, $this->name, $this->email, $this->roles) = unserialize($serialized);
}
But i still get "User must not be accessed before initialization". 🤔 So maybe implementing \Serializable is the old way to do it (from 2020).
I just need to update all my bundle. Like my last edit suggested: Somes of my bundles was too old.
I am building a form using Easy Admin's FormBuilder. My goal is to have an AssociationField which represents a OneToMany relationship, for example, to assign multiple products to a shop. Additionally, I only want some filtered products to be listed, so I overrode the createEditFormBuilder method in the CrudController, I used this question as reference, and this is the code for the overridden function :
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$filteredProducts = $context->getEntity()->getInstance()->getFilteredProducts();
$formBuilder->add('products', EntityType::class, ['class' => 'App\Entity\Product', 'choices' => $filteredProducts, 'multiple' => true]);
return $formBuilder;
}
I expected an Association field as the ones configured in the configureFields() function, however, the displayed field doesn't allow text search or autocomplete features, plus has incorrect height.
Expected:
Actual:
I tried to change the second argument in the $formBuilder->Add() function, but all specific EasyAdmin types threw errors.
UPDATE: I also tried using EasyAdmin's CrudFormType instead of EntityType, which doesn't support the 'choice' parameter. Still, the result was the same.
There is setQueryBuilder on the field, you can use it for filtering entities like this
<?php
// ...
public function configureFields(string $pageName): iterable
{
// ...
yield new AssociationField::new('products')->setQueryBuilder(function($queryBuilder) {
$queryBuilder
->andWhere('entity.id IN (1,2,3)')
;
})
;
// ...
}
With Symfony, I can get the content of request (POST and application/json) with :
$content = json_decode($request->getContent());
dd($content) return :
[
"subject" => "test"
"content" => "content"
]
I just want to hydrate an object with the subject, this works good :
$subject = new SubjectEntity();
$subject->setSubject($content['subject]);
But I don't want to do a setSubject() if the subject key doesn't exist.
I know I can do an if for each variable (Or $subject->setSubject($content['subject] ?? null)), but I think it will be a bit tedious in more complex cases.
Is there a way to "validate" the content sent to verify that all the desired keys are present ?
The cleanest solution would be a request param validator using symfony validator
Example:
public function validateRequest(array $params): array
{
$validator = Validation::createValidator();
$constraints = new Assert\Collection([
'subject' => new Assert\Type('string'),
'content' => new Assert\Type('string'),
]);
$group = new Assert\GroupSequence(['Default', 'custom']);
return $validator->validate($params, $constraints, );
}
If the keys can be optional, then you must use Assert\Optional(). Either way, you will need the null coalescing operator to cast to null if the key isn't set.
You can put these in a different class and with a small refactor you can make it work much nicer than doing a bunch of ifs, and it can be re-used to validate all of your api requests.
I'm managing the DataObject class 'trainer' with ModelAdmin. A trainer has a many_many relation to my other class 'language'.
On my 'trainer' class I'm manipulating the 'searchableFields' function to display a ListboxField in the filters area.
public function searchableFields() {
$languagesField = ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
return array (
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => $languagesField
)
);
}
That works like expected and shows me the wanted ListboxField. The Problem is, after selecting 1 or 2 or whatever languages and submitting the form, I'm receiving
[Warning] trim() expects parameter 1 to be string, array given
Is it possible here to filter with an many_many relation? And if so, how? Would be great if someone could point me in the right direction.
Update:
Full Error Message: http://www.sspaste.com/paste/show/56589337eea35
Trainer Class: http://www.sspaste.com/paste/show/56589441428d0
You need to define that logic within a $searchable_fields parameter instead of the searchableFields() which actually constructs the searchable fields and logic.
PHP would be likely to throw an error if you go doing fancy form stuff within the array itself, so farm that form field off to a separate method in the same DataObject and simply call upon it.
See my example, I hope it helps.
/* Define this DataObjects searchable Fields */
private static $searchable_fields = array(
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => self::languagesField()
)
);
/* Return the searchable field for Languages */
public function languagesField() {
return ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
}
Yes, it's possible. You just need to override two methods - one in Trainer data object and one in TrainerModelAdmin. First one will make a field, second one will do filtering.
Trainer Data Object:
public function scaffoldSearchFields($_params = null)
{
$fields = parent::scaffoldSearchFields($_params);
// get values from query, if set
$query = Controller::curr()->request->getVar('q');
$value = !empty($query['Languages']) && !empty($query['Languages']) ? $query['Languages'] : array();
// create a field with options and values
$lang = ListboxField::create("Languages", "Sprachen", Language::get()->map()->toArray(), $value, null, true);
// push it to field list
$fields->push($lang);
return $fields;
}
Trainer Model Admin
public function getList()
{
$list = parent::getList();
// check if managed model is right and is query set
$query = $this->request->getVar('q');
if ($this->modelClass === "Trainer" && !empty($query['Languages']) && !empty($query['Languages']))
{
// cast all values to integer, just to be sure
$ids = array();
foreach ($query['Languages'] as $lang)
{
$ids[] = (int)$lang;
}
// make a condition for query
$langs = join(",", $ids);
// run the query and take only trainer IDs
$trainers = DB::query("SELECT * FROM Trainer_Languages WHERE LanguageID IN ({$langs})")->column("TrainerID");
// filter query on those IDs and return it
return $list->filter("ID", $trainers);
}
return $list;
}
I needed to to add / update field collections to node entities without updating the node entities. I tried two ways listed in https://www.drupal.org/node/1842304 and http://alexrayu.com/blog/saveupdate-field-collection-without-nodesave but none of them seems to be working exactly the way I want.
I tried as follows:
$node = node_load($nid);
$field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_college_rating_data'));
$field_collection_item->setHostEntity('node', $node);
$field_collection_item->field_text1['und'][0]['tid'] = $form_state['input']['field_text1']['und'];
$field_collection_item->field_text2['und'][0]['value'] = $form_state['input']['field_text2']['und'];
$field_collection_item->save();
It added a field collection but it updates the node.
I also tried to alter the field collection form submit and used custom submit handler as follows:
function own_custom_field_collection_submit($form,$form_state) {
$field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state);
$field_collection_item->save(TRUE);
drupal_set_message(t('The changes have been saved.'));
$form_state['redirect'] = $field_collection_item->path();
}
I have copied this code from core field collection module to change the default argument to "TRUE" in the save function. It added the field collection but didn't associated with the parent node.
I need to save the field collection separately since my node entity form is very large with 50 to 60 fields and field collections and I don't want to update it as many times as I add / update any field collections to the node.
Any help will be much appreciated.
Thanks
You have to pragmatically relate your field collection to the host entity by the following code.
$field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state);
$field_collection_item->save(TRUE);
$host_entity = $field_collection_item->hostEntity();
$lang = 'und';
if (isset($host_entity->nid) && isset($host_entity->vid) && isset($lang) && isset($field_collection_item->item_id) && isset($field_collection_item->revision_id)) {
$query = db_select('field_data_field_text1', 'fd');
$query->addExpression('max(fd.delta)', 'total_row');
$query->condition('fd.entity_id', $host_entity->nid);
$max_delta = $query->execute()->fetchField();
if (isset($max_delta)) {
$max_delta = $max_delta + 1;
} else {
$max_delta = 0;
}
db_insert('field_data_{field_collection}')->fields(array(
'entity_type' => 'node',
'bundle' => '{node_type}',
'entity_id' => $host_entity->nid,
'revision_id' => $host_entity->vid,
'language' => $lang,
'delta' => $max_delta,
'field_text' => $field_collection_item->item_id,
'field_text_revision_id' => $field_collection_item->revision_id
))
->execute();
}
Replace your content type and field type in { } and then you just have your field collection relate to your host entity.
Thanks