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
Related
A Symfony 4 application has a User entity using Joined InheritanceType with types Admin, Staff and Volunteer. The Staff entity has a OneToOne relationship with an Organization entity. So the Organization form type includes a child staff, which has a custom form type NewUserType. (Relevant excerpts appear below.) If an empty OrganizationType form is submitted all of the expected form errors are rendered EXCEPT the name and email errors. The password form error field of NewUserType IS rendered. Symfony's profiler does not show errors for name or email. error_bubbling in NewUserType makes no difference. #Assert\Valid on the staff property of Organization entity makes no difference.
registerOrganization:
public function registerOrganiztion(Request $request) {
$form = $this->createForm(OrganizationType::class);
$templates = [
'Registration/organization.html.twig',
'Registration/new_user.html.twig',
'Registration/focuses.html.twig',
];
...
return $this->render('Default/formTemplates.html.twig', [
'form' => $form->createView(),
'headerText' => 'Add an organization',
'userHeader' => 'Staff Member',
'orgHeader' => 'Organization',
'focusHeader' => "Organization's Focus",
'templates' => $templates,
]);
}
new_user.html.twig:
{% if form.staff is defined %}{% set user = form.staff %}{% else %}{% set user = form %}{% endif %}
<div class="text-center text-bold">
{{ userHeader }}
</div>
{{ form_row(user.fname) }}
{{ form_row(user.sname) }}
{{ form_row(user.email) }}
{{ form_row(user.plainPassword.first) }}
{{ form_row(user.plainPassword.second) }}
OrganizationType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('staff', NewUserType::class)
}
NewUserType:
class NewUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('sname', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Last name: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('fname', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'First name: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('email', null, [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Email: ',
'label_attr' => ['class' => 'mr-2'],
])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'mapped' => false,
'constraints' => [new NotBlank(['message' => "Password may not empty"])],
'invalid_message' => 'Passwords do not match',
'first_options' => [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Password:',
'label_attr' => ['class' => 'mr-2'],
'required' => true,
],
'second_options' => [
'attr' => [
'class' => 'mb-2',
'size' => '15',
],
'label' => 'Confirm:',
'label_attr' => ['class' => 'mr-2'],
'required' => true,
],
))
;
if (Volunteer::class === $options['data_class']) {
$builder
->add('focuses', FocusFieldType::class)
->add('skills', SkillFieldType::class)
;
}
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => Staff::class,
'required' => false,
'error_bubbling' => true,
]);
}
}
Organization entity
/**
* Organization
*
* #ORM\Table(name="organization")
* #ORM\Entity
*/
class Organization
{
...
/**
* #ORM\OneToOne(targetEntity="Staff", inversedBy="organization")
* #ORM\JoinColumn(name="staff_id", referencedColumnName="id")
* #Assert\Valid
*/
protected $staff;
...
}
Staff entity
/**
* Staff
*
* #ORM\Table(name="staff")
* #ORM\Entity
*/
class Staff extends User
{
...
/**
* #ORM\OneToOne(targetEntity="Organization", mappedBy="staff")
*/
protected $organization;
public function getOrganization()
{
return $this->organization;
}
public function setOrganization(Organization $organization = null)
{
$this->organization = $organization;
return $this;
}
}
While I don't know why the name & email form errors are not rendered, they can be forced by adding the constraint to the form type. Just need then to remove the #Assert\... from those fields in the User entity.
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.
I use Sonata Admin and in list entity I need add my custom action, I did like the sonata document screen, but when I use filter field or click for page, screen, pagination my action disappears, and I have button select. How add custom action and how to make that is not lost?
class CRUDController extends Controller
{
public function cloneAction()
{
$object = $this->admin->getSubject();
$id = $object->getId();
return new RedirectResponse($this->generateUrl('admin_index', array('id' => $id))); //this is my custom action
}
}
add in service CRUD
<service id="sonata.admin.developer" class="Artel\AdminBundle\Controller\DeveloperSonataController">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Developer"/>
<argument />
<argument>Artel\ProfileBundle\Entity\Developer</argument>
<argument>ArtelAdminBundle:CRUD</argument>
<argument />
<call method="setTranslationDomain">
<argument>ArtelAdminBundle</argument>
</call>
<call method="addChild">
<argument type="service" id="sonata.news.admin.comment" />
</call>
</service>
create template
{# src/AppBundle/Resources/views/CRUD/list__action_clone.html.twig #}
//<a class="btn btn-sm" href="{{ admin.generateObjectUrl('clone', object) }}">clone</a>
add routing
class DeveloperSonataController extends Admin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('clone', $this->getRouterIdParameter().'/clone');
}
and in finished I have controller
class DeveloperSonataController extends Admin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('clone', $this->getRouterIdParameter().'/clone');
}
// Fields to be shown on create/edit forms
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))
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('email')
->add('telephone')
->add('skype')
->add('username')
->add('firstName')
->add('lastName')
->add('main_skill')
->add('skills')
->add('level')
->add('rate')
->add('english')
->add('location')
->add('country')
->add('created', 'doctrine_orm_date_range', array('field_type'=>'sonata_type_date_range_picker',))
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')//how change routig in addIdentifier??
->add('username')
->add('firstName')
->add('lastName')
->add('main_skill')
->add('level')
->add('rate')
->add('english')
->add('email')
->add('location')
->add('country')
->add('created')
->add('image', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_image.html.twig'))
->add('_action', 'actions', array(
'actions' => array(
'show' => array(),
'edit' => array(),
'clone' => array(
'template' => 'ArtelAdminBundle:CRUD:list__action_clone.html.twig'
)
)
))
;
}
}
I have two entities and two Admin classes(StripePage and Stripe) for them. Form looks good, it has my fields and button to add new subforms. When "add new" button is clicked, new subform with form from Admin class is rendered, but if you click second time on this button or try to save entity error appears:
Catchable Fatal Error: Argument 1 passed to
Fred\CoreBundle\Entity\StripePage::removeStripe() must be an instance
of Fred\CoreBundle\Entity\Stripe, null given in
C:\Users\lubimov\OpenServer\domains\tappic.dev\src\Fred\CoreBundle\Entity\StripePage.php
So symfony tries to remove entity instead of adding it.
StripePageAdmin class:
class StripePageAdmin extends Admin
{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id', null, array('label' => 'id'))
->add('name', null, array('label' => 'Page name'));
}
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', 'text', array('label' => 'Page name'))
->add('stripes', 'sonata_type_collection',
array('label' => 'Stripes', 'required' => false, 'by_reference' => false),
array('edit' => 'inline', 'inline' => 'table', 'sortable' => 'pos')
)
->end();
}
}
StripeAdmin class:
class StripeAdmin extends Admin
{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('picture', null, array('sortable' => true))
->add('text', null, array('sortable' => true))
->add('deep_link', null, array('sortable' => true));
}
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('file', 'file', array('label' => 'Picture'))
->add('text', null, array('label' => 'Text'))
->add('deep_link', 'choice', array(
'choices' => array('test1' => 'Test1', 'test' => 'Test2'),
'label' => 'Deep Link',
))
->end();
}
}
What is the problem? Stripe form in admin class must be configured by other way?
Well, solution that I use now is to set 'by_reference' => true in 'sonata_type_collection' settings.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', 'text', array('label' => 'Page name'))
->add('stripes', 'sonata_type_collection',
array('label' => 'Stripes', 'required' => false, 'by_reference' => true),
array('edit' => 'inline', 'inline' => 'table', 'sortable' => 'pos')
)
->end();
}
After this need to declare setStripes() method in entity.
public function setStripes(\Doctrine\Common\Collections\ArrayCollection $stripes) {
$this->stripes = $stripes;
}
If somebody finds how to solve this using addStripe() type of methods, please share it here.
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;
}