How should I handle Symfony2/Doctrine exceptions - php

Maybe this is a hot topic and some others talk about this but I don't find a good solution yet to this problem. Take this error for UNIQUE fields as example. When I try to insert the same values to the database I get this error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'j1234567' for key 'UNIQ_FC3A5A1592FC23A8'
Of course this happens on app_dev.php (development) enviroment but I don't know how to deal with this in order to show an error page to users instead of this ugly error. I test the same code at production then the ugly error disappear but I get this instead:
ERROR: INTERNAL SERVER ERROR
Paths, I though, are more than one, for example I could check the existence of the record before I insert or before I send the request trough AJAX but I want to learn how to achieve this by using Symfony2 and Doctrine2 asserts. I have already added this code to my entities:
<?php
....
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* SysPerfil
*
* #ORM\Entity
* #ORM\Table(name="sys_perfil")
* #UniqueEntity(fields={"rif"}, message="Este RIF ya existe en nuestra base de datos")
* #UniqueEntity(fields={"ci"}, message="Este CI ya existe en nuestra base de datos")
* #UniqueEntity(fields={"nombre"}, message="Este nombre ya existe en nuestra base de datos")
*/
class SysPerfil
{
....
But it's not working since I get the error mentioned above, so what is the best way to handle this? Any ideas? Advices? Docs?
Add form types
Yes, I send the data trough a form type, see below:
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('email', 'email', array(
'required' => true,
'label' => 'Email',
'trim' => true
))
->add('password', 'password', array(
'required' => true,
'label' => 'Contraseña',
'always_empty' => true
))
->add('confirm', 'password', array(
'required' => true,
'mapped' => false,
'label' => 'Verificar contraseña',
'always_empty' => true
))
->add('enabled', 'checkbox', array(
'required' => true,
'label' => 'Activo?',
'data' => true
))
->add('perfil', new AdminPerfilType());
}
And AdminPerfilType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('persJuridica', 'choice', array(
'choices' => RifType::getChoices(),
'required' => true,
'label' => 'RIF',
'trim' => true,
'attr' => array(
'class' => 'persJuridica'
)
))
->add('roleType', 'choice', array(
'choices' => AdminRoleType::getChoices(),
'required' => true,
'label' => "Tipo de Usuario",
'trim' => true
))
->add('rif', 'text', array(
'required' => true,
'label' => false,
'trim' => true,
'attr' => array(
'class' => "numeric",
'maxlength' => 15
)
))
->add('ci', 'text', array(
'label' => 'CI',
'trim' => true,
'attr' => array(
'class' => "numeric ci",
'disabled' => 'disabled'
)
))
->add('nombre', 'text', array(
'required' => true,
'label' => 'Nombre',
'trim' => true
))
->add('apellido', 'text', array(
'required' => true,
'label' => 'Apellidos',
'trim' => true
));
}
If you're looking for validation rules inside the form then I haven't since I though that Doctrine/Symfony2 handle that part already

I guess your error is because you have a Parent -> Child Entities with One-To-One mapping, your form validation is checking the parent entity validation rules without checking the child validation rules because you are not using Assert\Valid
Example from Symfony Documentation http://symfony.com/doc/current/reference/constraints/Valid.html:
// src/Acme/HelloBundle/Entity/Address.php
namespace Acme\HelloBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Address
{
/**
* #Assert\NotBlank()
*/
protected $street;
/**
* #Assert\NotBlank
* #Assert\Length(max = "5")
*/
protected $zipCode;
}
// src/Acme/HelloBundle/Entity/Author.php
namespace Acme\HelloBundle\Entity;
class Author
{
/**
* #Assert\NotBlank
* #Assert\Length(min = "4")
*/
protected $firstName;
/**
* #Assert\NotBlank
*/
protected $lastName;
//without this Symfony won't check if the inserted address is satisfying the validation rules or not
/**
* #Assert\Valid
*/
protected $address;
}

Related

Expected argument of type "int or null", "object" given at property path "id_carrier" [duplicate]

This question already has an answer here:
"Expected argument of type "int or null", "object" given at property path
(1 answer)
Closed 5 months ago.
I'm working on a Symfony 5.4 project and i have some troubles.
I have already take a look on stackoverflow, but i can't found a solution!
I have a problem, because i have create a form for make a search bar in my page. So, in the form there are multiple input.
In two of this input, i will show values with 2 query (they are two separate entity's, one from a table called "en_carrier" and the other one in a table calles en_order_state. In the specific, the ID of this tables). This two input have to return me integer values.
NOTE: the form will not interact withe tables en_order_state or en_carrier, but with another table that contain the ID's of these two.
So i thought the best solution would be a EntityType for these two, i can create a specific query for obtain all the values.
Apparently, all is correct: label, name and values. But when i submit the form, i get the error
Expected argument of type "int or null", "object" given at property path "id_carrier".
How can i resolve this problem?
I decide to structure my form in this way:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('id_state', IntegerType::class, [
'required' => false
])
->add('id_carrier', EntityType::class, [
'class' => Carrier::class,
'required' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
},
'choice_label' => 'name',
'choice_value' => function(?Carrier $carrier){
return $carrier? (int)$carrier->getId():"";
},
'label' => 'Corriere',
])
->add('order_state', EntityType::class, [
'required' => false,
'class' => OrderState::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('os')
->where('os.id_lang = 1')
->orderBy('os.state', 'ASC');
},
'choice_label' => 'state',
'choice_value' => function(?OrderState $orderState){
return $orderState? (int)$orderState->getId():"";
},
'label' => 'Stato Ordine',
])
->add('payment', TextType::class, [
'required' => false,
'attr' => array(
'class' => 'col-lg-2 control-label text-center'
)
])
->add('ps_reference', TextType::class, [
'required' => false,
'attr' => array(
'class' => 'col-lg-2 control-label text-center',
'maxlength' => 9
)
])
->add('en_reference', TextType::class, [
'required' => false,
'attr' => array(
'class' => 'col-lg-2 control-label text-center'
)
])
->add('invoice', ChoiceType::class, [
'required' => false,
'label' => 'Fattura',
'choices' => [
'no' => false,
'si' => true
]
])
->add('date', DateType::class, [
'required' => false
])
->add('submit', SubmitType::class);
;
}
These are my two entity that i use in the form
Carrier
class Carrier
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id_carrier;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
Order State
/**
* #ORM\Entity(repositoryClass=OrderStateRepository::class)
* #ORM\Entity #ORM\Table(name="en_order_state")
*/
class OrderState
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id_state;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $id_lang;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $state;
I choise use EntityType because i have a lot of values to show, and i can do this with a query.
But when i submit the form, i get this error:
I found the solution, i want not set the values of form in table of entity's correspondents, so it's enough to add "mapped" => false in the paramters of form.

Make an admin form listen for validation from child admins

I am adding features to an application based on Symfony 2.8 and Sonata.
The application already has a Page entity and a PageAdmin class. I want to add a nested set of Synonym entities on every page, so I make my PageAdmin's configureFormFields() method look like this:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title')
->add('synonym', 'sonata_type_collection', array(
'label' => "Synonyme",
'cascade_validation' => true,
'required' => false,
'error_bubbling' => true,
), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('contentBlock', 'sonata_type_collection', array(
'label' => "Inhalt",
'cascade_validation' => true,
'required' => false
), array(
'edit' => 'inline',
'inline' => 'table'
))
;
}
... which generally works pretty well. The only problem is that when I leave one of the required fields in my Synonym entity blank, the application does not give me a pretty red "flash" message scolding me for my omission. Instead, it throws an exception and returns 500 status, which is not what I want to see:
Failed to update object: Application\Sonata\PageBundle\Entity\Page 500
Internal Server Error - ModelManagerException 3 linked Exceptions:
NotNullConstraintViolationException » PDOException » PDOException »
...
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name'
cannot be null
Is there a way to make omissions from the Synonym fields get flagged nicely for the user, rather than throwing and exception and returning a 500 status?
=====
Update 1: Here is the content of the configureFormFields() method in my SynonymAdmin class:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', null, ['label' => 'Name *', 'required' => true, 'error_bubbling' => true,])
->add('title', null, ['label' => 'Titel', 'required' => false, 'error_bubbling' => true,])
->add('visible', null, ['label'=>'Sichtbarkeit', 'required' => false, 'error_bubbling' => true,])
;
}
Update 2: Here is the Synonyms definition in my entity class.
/**
* #var ArrayCollection
*
* #Assert\NotBlank
*
*/
private $synonyms;
... and from Synonym.php:
/**
* #var string
*
* #Assert\NotBlank
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
For starters I think you can add 'required' => true to the fields in your SynonymAdmin to trigger html5 validation.
besides that you can add validation rules to your entity and Sonata should pick up on that.
class Page
{
/**
* #Assert\Valid
*/
protected $synonyms;
}
class Synonym
{
/**
* #Assert\NotBlank
*/
private $name;
}

ZF2 multi-select form preselect values ManyToMany

I can't get to preselect values in a multiselect form element representing a many to many relation.
In my model $admin I have the proper data : an ArrayCollection containing the correct CampsTypes but in the form I can't get the multiselect to preselect the proper options.
Admins model
/**
* #var ArrayCollection CampsTypes $campstypes
*
* #ORM\ManyToMany(targetEntity="CampsTypes", inversedBy="admins", cascade={"persist"})
* #ORM\JoinTable(name="campstypes_admins",
* joinColumns={#ORM\JoinColumn(name="admins_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="campstypes_id", referencedColumnName="id")}
* )
*/
private $campstypes;
CampsType model
/**
* #var ArrayCollection Admins $admins
*
* #ORM\ManyToMany(targetEntity="Admins", mappedBy="campstypes", cascade={"persist"})
*/
private $admins;
Then I define my form select element as follow
[
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'campTypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
And finally here is my action to receive the form
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campTypes'])) {
$ids = $form->get('campTypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampTypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
I'm getting out of solution to try.
Any idea what I am doing wrong ?
Have you read this ? I have the feeling you're looking for the ObjectMultiCheckbox instead of the ObjectSelect Form Element.
Examples from my own code
Usage for a single select (use case: set/change a default currency for some other entity)
$this->add([
'type' => ObjectSelect::class,
'required' => true,
'name' => 'defaultCurrency',
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Currency::class,
'property' => 'id',
// Use these commented lines if you wish to use a Repository function ('name' => 'repositoryFunctionName')
// 'is_method' => true,
// 'find_method' => [
// 'name' => 'getEnabledCurrencies',
// ],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Default currency'),
'label_attributes' => [
'class' => '',
'title' => _('Default currency'),
],
'label_generator' => function ($targetEntity) {
/** #var Currency $targetEntity */
return $targetEntity->getName(); // Generates option text based on name property of Entity (Currency in this case)
},
],
]);
Usage for multiple select (Use case: add/remove (multiple) roles to/from user)
$this->add([
'name' => 'roles',
'required' => false,
'type' => ObjectMultiCheckbox::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Role::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Roles'),
'label_generator' => function ($targetEntity) {
/** #var Role $targetEntity */
return $targetEntity->getName();
},
],
]);
As a side note: you should really use factories more. I see you using the ServiceLocator throughout your class code, you can avoid that by injecting your needs via a Factory.
If you need more information, I suggest you have a look at a bunch of my past questions as well. I had quite a few, starting out similar to what you're looking for. Managed to figure quite a few of them out on my own and have tried to describe the solutions in depth.
So I made it work !
basically my main problem is that I was not using proper Doctrine naming convention for the fields name of the form.
So I had to reverse engineer my DB into doctrine entities to compare with what I had done to understand where it was not working.
And also to set the model in both end (admins in camptype and camptypse in admin) for the whole shabang to work.
Here are the working classes :
Admin form:
[
'type' => ObjectSelect::class,
'name' => 'campstypes',
'required' => false,
'options' => [
'object_manager' => $this->getServiceLocator()->get(EntityManager::class),
'target_class' => CampsTypes::class,
'property' => 'title',
'label' => 'Type de camps autorisés',
'instructions' => 'Ne rien sélectionner si edition d\'un super admin',
],
'attributes' => [
'class' => '',
'multiple' => 'multiple',
]
],
Admin Controller:
protected function saveAdmin(Admins &$admin, &$form, &$msg)
{
$em = $this->getEntityManager();
/** #var CampTypesService $serviceCampTypes */
$serviceCampTypes = $this->getServiceLocator()->get(CampTypesService::class);
$form->bind($admin);
if ($this->getRequest()->isPost()) {
$data = $this->getRequest()->getPost();
if (empty($data['password'])) {
$form->remove('password');
}
$form->setData($data);
if ($form->isValid()) {
if (isset($data['campstypes'])) {
$ids = $form->get('campstypes')->getValue();
$campsTypes = new ArrayCollection($serviceCampTypes->getCampTypesByIds(array_values($ids)));
foreach ($campsTypes as &$campsType) {
/** #var CampsTypes $campsType*/
$campsType->addAdmin($admin);
}
$admin->setCampstypes($campsTypes);
}
$em->persist($admin);
$em->flush();
$msg = 'Sauvegarde des données effectuée';
return;
}
}
return;
}
So by renaming properly the fields of my form and models and setting the data in both end model of the relation I got it to work.

Symfony 3 - CollectionType of EntityType - Ajax submission (FOS REST)

I have form which is associating barcodes with assets.
There can be multiple barcodes associated with each asset.
The Entity entries for the Asset and Barcodes are:
Asset Entity -> barcodes
/**
* #var ArrayCollection $barcodes
* #ORM\ManyToMany(targetEntity="Barcode", cascade={"persist"})
* #ORM\OrderBy({"updated" = "DESC"})
* #ORM\JoinTable(name="asset_barcode",
* joinColumns={#ORM\JoinColumn(name="asset_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="barcode_id", referencedColumnName="id", unique=true, nullable=false)}
* )
*/
protected $barcodes;
Barcode Entity -> barcode
/**
* #var string
*
* #ORM\Column(type="string", length=64, nullable=true)
* #ORM\ManyToMany(targetEntity="Asset", mappedBy="barcodes", cascade={"persist", "remove"})
*/
private $barcode;
AssetType form
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add( 'id', HiddenType::class, ['label' => false] )
->add( 'serial_number', TextType::class, ['label' => false] )
->add( 'model', TextType::class, [
'label' => 'common.model'
] )
->add( 'location', EntityType::class, [
'class' => 'AppBundle:Location',
'choice_label' => 'name',
'multiple' => false,
'expanded' => false,
'required' => true,
'label' => 'asset.location',
'choice_translation_domain' => false
] )
->add( 'barcodes', CollectionType::class, [
'label' => 'asset.barcode',
'entry_type' => BarcodeType::class,
'by_reference' => false,
'required' => false,
'label' => false,
'empty_data' => null,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'mapped' => false,
'prototype_name' => '__barcode__'
] )
->add( 'comment', TextType::class, [
'label' => false
] )
->add( 'active', CheckboxType::class, ['label' => 'common.active'] )
;
$builder->get( 'model' )
->addModelTransformer( new ModelToIdTransformer( $this->em ) );
$builder->get( 'barcodes' )
->addModelTransformer( new BarcodeToEntityTransformer( $this->em ) );
}
The BarcodeToEntityTransformer is receiving null data. I determined this by using dump and die.
BarcodeType form
class BarcodeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add( 'id', HiddenType::class, ['label' => false] )
->add( 'updated', HiddenType::class, ['label' => false, 'disabled' => true] )
->add( 'barcode', TextType::class, [
] )
->add( 'comment', TextType::class, [
'label' => false
] )
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions( OptionsResolver $resolver )
{
$resolver->setDefaults( array(
'data_class' => 'AppBundle\Entity\Barcode'
) );
}
public function getName()
{
return 'barcode';
}
}
barcodes prototype
<div id="asset_barcodes" data-prototype=" <div class="form-row barcode"><input type="hidden" id="asset_barcodes___barcode___updated" name="asset[barcodes][__barcode__][updated]" disabled="disabled" /><span class="input"><input type="text" id="asset_barcodes___barcode___barcode" name="asset[barcodes][__barcode__][barcode]" /></span><span class="comment"><input type="text" id="asset_barcodes___barcode___comment" name="asset[barcodes][__barcode__][comment]" /></span><span class="remove-form-row" title="Remove" id="barcode-__barcode__"><i class="fa fa-remove"></i></span></div> "></div>
My problem is that the barcode data does not seem to be seen when submitted with application/json.
There is a little extra data being sent as a side effect of the page architecture.
How should the barcodes data be submitted in order to be read properly by the form? Or, what changes do I need to make to the form?
The problem is, apparently, with 'mapped' => false,
This option is used for the fields of the form, which are unrelated to a model (f.e. for a "accept rules" checkboxes or stuff like this)
Symfony in this case does not process data from this field in any way and does not set it to model.

Symfony Sonata admin find by some field

I install SonataAdminBundle and create controller extends Admin and function configureFormFields, configureDatagridFilters, configureListFields. And in field list I use field image but I see only url for image, my image live in amazon S3 I want see image in table. How I do this? And I have filter for find by colums, my entity developer have array skill and by one skill find good but how find for two or many skill ?
And I add for admin can do upload avatar for developer but in my action(not extends Admin) I upload like this for(field image in Developer = string and I set just url for S3)
$url = sprintf(
'%s%s',
$this->container->getParameter('acme_storage.amazon_s3.base_url'),
$this->getPhotoUploader()->upload($request->files->get('file'), $user_company_name)
);
$user->setImage($url);
how I can do for sonata, reload controller? How I do this ?
this my action:
class DeveloperAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('firstName', null, array('label' => 'Developer\'s First Name', 'max_length' => 255))
->add('lastName', null, array('label' => 'Developer\'s Last Name', 'max_length' => 255))
->add('qualification', 'choice', array('label' => 'Speciality',
'choices' => array('Frontend' => 'Frontend', 'Backend' => 'Backend', 'Full stack' => 'Full stack'),'attr'=> array('class'=>'qualif'), 'required' => false))
->add('level', 'choice', array('label' => 'Professional Level', 'max_length' => 255,
'choices' => array('Junior' => 'Junior', 'Middle' => 'Middle', 'Senior' => 'Senior')))
->add('tags', 'tags', array('label' => 'Tags','required' => false))
->add('main_skill', 'mainSkill', array('label' => 'Main Skill', 'required' => true, 'mapped' => true, 'attr' => array('placeholder' => 'Select your skills ...', 'class'=>'main_skill') ))
->add('skills', 'skills', array('label' => 'Skills','required' => false))
->add('english', 'choice', array('label' => 'English Level', 'max_length' => 255,
'choices' => array('Basic' => 'Basic', 'Intermediate' => 'Intermediate', 'Advanced' => 'Advanced')))
->add('rate', null, array('label' => 'Rate $/h', 'max_length' => 255));
$image = $this->getSubject();
$fileFieldOptions = array('required' => false);
if ($image && ($webPath = $image->getImage())) {
dump($image);exit; //I have all user and field image local url /temp/sdgsdg
$container = $this->getConfigurationPool()->getContainer();
$fullPath = $container->get('request')->getBasePath().'/'.$webPath;
$fileFieldOptions['help'] = '<img src="'.$fullPath.'" class="admin-preview" />';
}
$formMapper
->add('image', 'file', $fileFieldOptions)
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('firstName')
->add('lastName')
->add('main_skill')
->add('skills')
;
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('username')
->add('firstName')
->add('lastName')
->add('main_skill')
->add('skills')
->add('image', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_image.html.twig'))
->add('_action', 'actions', array(
'actions' => array(
'show' => array(),
'edit' => array(),
)
))
;
}
}
How find by two or many skill ????
this is my entity:
class Developer extends CustomUser
{
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, unique=false, nullable=true)
* #Assert\Length(min=3, max=255)
*/
protected $email;
////
/**
* #var string
*
* #ORM\Column(name="skills", type="array")
*/
private $skills = array();
and in table for my developer in colum skill I see:
[0 => SOAP] [1 => Cisco] [2 => PHP] [3 => Sugar Crm] [4 => Hibernate] [5 => Java ME]
but when I add developer I use my service for skill and I see norm skill:
xPHP, xJava
How can fix this problem, reload template or controller ? Help please
For this task use https://sonata-project.org/bundles/media/2-2/doc/index.html . It's very easy

Categories