I've been struggling with this for a few days now, so I hope somebody can help.
I have a OneToMany relation between a table Resources and my FOSUserBundle provided User entity, so that one user can have posted many resources.
Note: the entities have been edited for brevity, however I don't believe I removed anything important for this question
My Resources entity:
<?php
namespace SFI\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="SFI\MainBundle\Entity\ResourcesRepository")
* #ORM\Table(name="resources")
*/
class Resources {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id()
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\Column(type="string", length=255)
*/
protected $type;
/**
* #ORM\Column(type="string", length=255)
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $description;
/**
* #ORM\Column(type="datetime")
*/
protected $created_time;
/**
* #ORM\ManyToOne(targetEntity="SFI\UserBundle\Entity\User", inversedBy="created_by")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
protected $created_by;
---rest of getters and setters---
/**
* Set created_by
*
* #param \SFI\UserBundle\Entity\User $createdBy
* #return Resources
*/
public function setCreatedBy(\SFI\UserBundle\Entity\User $createdBy = null)
{
$this->created_by = $createdBy;
return $this;
}
/**
* Get created_by
*
* #return \SFI\UserBundle\Entity\User
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
My User entity:
<?php
namespace SFI\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="SFI\UserBundle\Entity\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max="255",
* minMessage="The name is too short.",
* maxMessage="The name is too long.",
* groups={"Registration", "Profile"}
* )
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return User
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #ORM\OneToMany(targetEntity="SFI\MainBundle\Entity\Resources", mappedBy="created_by")
*/
protected $created_by;
/**
* Add created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
* #return User
*/
public function addCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by[] = $createdBy;
return $this;
}
/**
* Remove created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
*/
public function removeCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by->removeElement($createdBy);
}
/**
* Get created_by
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
I have a couple of Form Types but the main one I'm working with is ResourceType:
<?php
namespace SFI\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ResourceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//Setting date and time
$created_at = new \DateTime('now');
$builder
->add('name', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Resource name',
),
))
->add('type', 'choice', array(
'required' => true,
'empty_value' => 'Choose a type',
'choices' => array('w' => 'Website', 'v' => 'Video', 'a' => 'Audio'),
'attr' => array(
'class' => 'form-control',
),
))
->add('link', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Add a link',
),
))
->add('description', 'textarea', array(
'required' => true,
'attr' => array(
'class' => 'textarea',
'style' => 'width: 100%; height: 200px; font-size: 14px; line-height: 18px; border: 1px solid #dddddd; padding: 10px;',
'placeholder' => 'Write a description...',
),
))
->add('created_time', 'datetime', array(
'disabled' => true,
'data' => $created_at,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-primary'),
));
}
public function getName()
{
return 'resource';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SFI\MainBundle\Entity\Resources',
));
}
}
My controller for the Resource form part:
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resources();
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
$session = $this->getRequest()->getSession();
//$session->getFlashBag()->add('notice', 'Form processed');
$session->getFlashBag()->add('notice', 'Form processed, testing, no persist');
//$em->persist($resource);
//$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
What I need to do is relate these entities when a new Resource is created, to "assign" it to the logged in user via the relation in the tables.
I've looked into embedded forms and other suggestions on Symfony's documentation, but I can't wrap my head around how to apply what I read to this specific example. I have followed the example with the tasks and the tags and the categories and all that, but I can't understand how it applies to my situation, having the User entity in another bundle, and a User form type that relates the user to their school and just adds an extra full name field, while inheriting FOSUserBundle's original form type.
I also have a funny feeling that I haven't tackled or "layed out" the relationship correctly in my code so as to make it easier to understand which objects relate.
Any help would be much appreciated! Also please let me know if you require other code or further information.
You should just be able to set the user manually before persisting the Resources entity.
if ($newResourceForm->isValid()) {
...
$resource->setCreatedBy($this->getUser());
$em->persist($resource);
$em->flush();
...
}
Symfony makes use of the $this->getUser() shortcut in controllers (see http://symfony.com/doc/current/book/security.html#retrieving-the-user-object). I'm assuming you only allow this form when the user is logged in.
Also if you are using Doctrine I would recommend using it to generate your entities and getters/setters for you, as it will generate sensible names that coincide with Symfony conventions. For instance, your entity would end up being named 'Resource' vs. 'Resources', and your variables would be camelCased ($createdBy vs. $created_by).
OK so based on Jason's suggestion, and the previous one by sjagr, I have renamed a few things for consistency and better understand, and I have written the following, which works correctly!
Thank you all for your help!
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resource();
$createdBy = $em->getRepository('SFIUserBundle:User')->find($this->getUser()->getId());
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
//Getting current date and time
$created_time = new \DateTime('now');
$resource->setCreatedTime($created_time);
$resource->setCreatedBy($createdBy);
$session = $this->getRequest()->getSession();
$session->getFlashBag()->add('notice', 'Form processed');
$em->persist($resource);
$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
All I needed was to pass the current user's entire object in setCreatedBy and the relation works correctly.
Thank you all again!
Related
my problem concerns one field from User form, namely Username. Since assertion validation works for other fields in the entity I find it odd to behave like this - skipping the Assertion rule I did point for username attribute in User entity and passing null attribute to userFormNewHandler which is generating an error, especially when I find it not really different than other fields. I wonder, what am I missing?
UserType.php:
<?php
namespace App\UserBundle\Form\Type;
use App\UserBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, ['required'=>true,
'invalid_message' => 'Username must not be empty!'])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'mapped' => false,
'first_options' => array('label' => 'New password'),
'second_options' => array('label' => 'Confirm new password'),
'invalid_message' => 'The password fields must match.',
'required' => true,
'constraints' => [
new NotBlank([
'message' => 'Password field must not be blank!'
])]
))
->add('active_status', ChoiceType::class, [
'choices' => [
'Active' => true,
'Inactive' => false,
],])
->add('first_name',TextType::class, [
'required'=>true, 'invalid_message' => 'First name must not be empty!'])
->add('last_name', TextType::class, [
'required'=>true, 'invalid_message' => 'Last name must not be empty!'])
->add('email', EmailType::class, [
'required'=>true, 'invalid_message' => 'Email must not be empty!']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
And this is my User.php entity:
<?php
namespace App\UserBundle\Entity;
use App\UserBundle\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Assert\NotBlank(message="Fill username field")
*/
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="boolean")
*/
private $active_status;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank(message="Fill first name field")
*/
private $first_name;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank(message="Fill last name field")
*/
private $last_name;
/**
* #ORM\Column(type="string", length=40)
* #Assert\NotBlank(message="Fill email field")
*/
private $email;
/**
* Representation of account status
*/
public function getActiveStatus(): bool
{
return $this->active_status;
}
/**
* Setting account status
*/
public function setActiveStatus(bool $active_status): self
{
$this->active_status = $active_status;
return $this;
}
/**
* Representation of username
*/
public function getUsername(): string
{
return (string) $this->username;
}
/**
* Setting username for user
*/
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
//...
and also _form.html.twig, where form is being rendered:
{{ form_start(form, { attr: {novalidate: 'novalidate'} }) }}
{{ form_widget(form) }}
<button class="btn" >{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
I found it! That was because I did point out in setter Username's method to look strictly for string and I guess that prevented me from passing null attribute to the form. Now PHP validation works correctly :)
Part generating error:
User.php
/**
* Setting username for user
*/
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
Fix:
/**
* Setting username for user
*/
public function setUsername($username): self
{
$this->username = $username;
return $this;
}
I started project with predefined database structure and generated Entities from database structure using commands from console. I am creating bus company. Bus can have many amenities (i.e. Many buses can have many amenities).
I have a ManyToMany table and for some reason data inside that table is not saving. Other form data is saved in other table but this one still remains empty.
Does someone knows where is the problem?
Here is the code:
BusVehicles.php
/**
* Many Buses have Many Amenities.
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="bus_amenities")
* #ORM\JoinTable(
* name="bus_amenities",
* joinColumns={
* #ORM\JoinColumn(name="bus_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="amenities_id", referencedColumnName="id")
* }
* )
*/
private $amenities;
/**
* Add items
*
* #param \AdminBundle\Entity\Amenities $amenities
*/
public function addAmenities(Amenities $amenities)
{
if ($this->amenities->contains($amenities)) {
return;
}
$this->amenities->add($amenities);
$amenities->addBusAmenities($this);
}
/**
* Remove amenities
*
* #param \AdminBundle\Entity\Amenities $amenities
*/
public function removeAmenities(Amenities $amenities)
{
if (!$this->amenities->contains($amenities)) {
return;
}
$this->amenities->removeElement($amenities);
$amenities->removeBusAmenities($this);
}
/**
* Get Amenities
*
* #return ArrayCollection
*/
public function getAmenities()
{
return $this->amenities;
}
// ...
public function __construct() {
$this->amenities = new ArrayCollection();
}
}
Amenities.php
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="amenities")
*/
private $bus_amenities;
public function __construct() {
$this->bus_amenities = new ArrayCollection();
}
/**
* Get bus amenities
*
* #return ArrayCollection
*/
public function getBusAmenities()
{
return $this->bus_amenities;
}
/**
* Add bus amenities
*
* #param \AdminBundle\Entity\BusVehicles
*/
public function addBusAmenities(BusVehicles $amenities)
{
if ($this->bus_amenities->contains($amenities)) {
return;
}
$this->bus_amenities->add($amenities);
$amenities->addAmenities($this);
}
/**
* Remove bus amenities
*
* #param \AdminBundle\Entity\BusVehicles
*/
public function removeBusAmenities(BusVehicles $amenities)
{
if (!$this->bus_amenities->contains($amenities)) {
return;
}
$this->bus_amenities->removeElement($amenities);
$amenities->removeAmenities($this);
}
Controller:
class BusController extends Controller
{
/**
* #Template
*/
public function addAction(Request $request)
{
$bus = new BusVehicles();
$form = $this->createForm(BusVehiclesType::class, $bus);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
/* $amenities = $em->getAmenities();
foreach($amenities as $amenitie){
$em->persist($amenitie);
}*/
$em->persist($bus);
$em->flush();
// Adding flash message to our user
$request->getSession()
->getFlashBag()
->add('success', 'New vehicle successfully added');
// return $this->redirectToRoute('bus_add');
}
return [
'form' => $form->createView(),
];
}
}
EDIT 1: Added form
class BusVehiclesType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('licencePlate')
->add('manufacturer')
->add('company', EntityType::class, array(
'class' => 'AdminBundle:Companies',
'choice_label' => 'name',
))
->add('busType', EntityType::class, array(
'class' => 'AdminBundle:BusTypes',
'choice_label' => 'type',
))
->add('emissionClass', EntityType::class, array(
'class' => 'AdminBundle:BusEmissionClasses',
'choice_label' => 'name',
))
->add('fuelType', EntityType::class, array(
'class' => 'AdminBundle:BusFuelTypes',
'choice_label' => 'fuel_type',
))
->add('amenities', EntityType::class, array(
'class' => 'AdminBundle:Amenities',
'choice_label' => 'name',
'multiple' => true,
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AdminBundle\Entity\BusVehicles'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'adminbundle_busvehicles';
}
}
I think the problem is in your BusVehicles.php, Check relation and change as per below:
/**
* Many Buses have Many Amenities.
* #ORM\ManyToMany(targetEntity="Amenities", inversedBy="bus_amenities", cascade={"persist"})
*
*/
private $amenities;
And in Aminities Entity change as per below:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="BusVehicles", mappedBy="amenities", cascade={"persist"})
*/
private $bus_amenities;
In Sonata I have created several lists and all work fine. (Please not that that was a while ago, so I may have done something there which fixed the issue I will describe here...).
Now I have created a new listing of Playlist entities:
As you can see in the picture, both the "Id" column and the "Aangemaakt op" columns are sortable, however the "Playlist" column is not.
Both the "Aangemaakt op" and the "Playlist" fields are date-fields, but since the "Aangemaakt op" field is sortable I would say that has nothing to do with it.
I have been searching the Sonata documentation, Google and StackOverflow, but haven't found any clue concerning this issue. I did find thread about sorting a list based on an Entity field, but my field isn't an entity.
Relevant code:
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('id')
->add('playlist_date', 'date', array('label' => 'Playlist'))
->add('created', 'datetime', array('label' => 'Aangemaakt op'))
->add(
'_action', 'actions', array(
'actions' => array(
'delete' => array(),
)
)
);
}
Some StackOverflow threads and an answer below mention adding 'sortable' => true to the field that must be sortable.
Doing that indeed makes the column clickable to sort, but clicking it results in the following exception:
Catchable Fatal Error: Argument 1 passed to
Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery::entityJoin()
must be of the type array, null given, called in
/path/of/my/project/sonata-project/doctrine-orm-admin-bundle/Datagrid/ProxyQuery.php
on line 142 and defined.
According to other StackOverflow threads that is because a join must be created. However, the field is simply a field of the same Mysql record as the other fields. I did find a StackOverflow thread mentioning this as well and in which they joined the same record in order to make this work, but I didn't get that to work. Besides, I thank that shouldn't be the way to order the contents of a column.
Does anyone have a clue?
Update in reaction to Hibatallah Aouadni's answer
As Hibatallah suggests, I added the following to my PlaylistsAdmin:
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('playlist_date')
->add('created');
}
This resulted in the following error message:
Notice: Undefined index: playlist_date
So I inspected my Entity and I found that it has a UniqueConstraint:
uniqueConstraints={#ORM\UniqueConstraint(name="playlist_date", columns={"playlist_date"})}
It does not have an actual "index" defined, but ofcourse it is. However as a test I added the following:
, indexes={#ORM\Index(name="playlist_date", columns={"playlist_date"})}
This didn't give any different result.
So still no luck at all :(
** Entity and Entity admin **
Entity Admin:
<?php
namespace Company\AdminBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\AdminBundle\Route\RouteCollection;
class PlaylistsAdmin extends Admin
{
protected $baseRoutePattern = 'playlists';
protected $baseRouteName = 'playlists';
protected function configureRoutes(RouteCollection $collection) {
$collection->clearExcept(array('list', 'delete', 'show'));
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('id')
->add('playlist_date', 'date', array('label' => 'Playlist'))
->add('created', 'datetime', array('label' => 'Aangemaakt op'))
->add(
'_action', 'actions', array(
'actions' => array(
'show' => array(),
/*'edit' => array(),*/
'delete' => array(),
)
)
);
}
public function getBatchActions() {
return array();
}
}
Entity:
<?php
namespace Company\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Playlist
*
* #ORM\Table(name="playlist", uniqueConstraints={#ORM\UniqueConstraint(name="playlist_date", columns={"playlist_date"})})
* #ORM\Entity()
*/
class Playlist
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var \DateTime
*
* #ORM\Column(name="playlist_date", type="date", nullable=true)
*/
protected $playlistDate;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime", nullable=true)
*/
protected $created;
/**
* #var \Doctrine\Common\Collections\Collection
*/
protected $video;
/**
* Constructor
*/
public function __construct() {
$this->video = new \Doctrine\Common\Collections\ArrayCollection();
$this->setCreated(new \DateTime());
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set playlistDate
*
* #param \DateTime $playlistDate
* #return Playlist
*/
public function setPlaylistDate($playlistDate) {
$this->playlistDate = $playlistDate;
return $this;
}
/**
* Get playlistDate
*
* #return \DateTime
*/
public function getPlaylistDate() {
return $this->playlistDate;
}
/**
* Set created
*
* #param \DateTime $created
* #return Playlist
*/
public function setCreated($created) {
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated() {
return $this->created;
}
/**
* Add video
*
* #param \Company\AppBundle\Entity\Video $video
* #return Playlist
*/
public function addVideo(\Company\AppBundle\Entity\Video $video) {
$this->video[] = $video;
return $this;
}
/**
* Remove video
*
* #param \Company\AppBundle\Entity\Video $video
*/
public function removeVideo(\Company\AppBundle\Entity\Video $video) {
$this->video->removeElement($video);
}
/**
* Get video
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getVideo() {
return $this->video;
}
}
Finally I found it, it's so absurd, in the configureListFields method, you have to call the attribute with its name not the DataBase name, so:
change
->add('playlist_date', 'date', array('label' => 'Playlist'))
to
->add('playlistDate', 'date', array('label' => 'Playlist'))
:D I can't beleive we spend all this time for some absurd mistake ;)
all you need to do is to add the argument sortable to array and true as value:
->add('playlist_date', 'date', array(
'label' => 'Playlist',
'sortable' => true
))
try to add the playlist field in configureDatagridFilters in your entity admin:
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('playlist_date')
->add('created');
}
and it will work ;)
I would like to know with the following code, how to display in the generated form, a set of Contacts, linked to the Company of this note, instead of all contacts in the DB?
Entity Note :
/**
* #ORM\ManyToOne(targetEntity="Main\MainBundle\Entity\NoteType")
* #ORM\JoinColumn(nullable=false)
*/
private $noteType;
/**
* #ORM\ManyToOne(targetEntity="Main\MainBundle\Entity\Contact", inversedBy="contacts")
* #ORM\JoinColumn(nullable=true)
*/
private $contact;
/**
* #ORM\ManyToOne(targetEntity="Main\MainBundle\Entity\Company")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="Main\MainBundle\Entity\User", inversedBy="users")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
Entity Company :
/**
* #ORM\OneToMany(targetEntity="Main\MainBundle\Entity\Contact", mappedBy="company", cascade={"remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $contacts;
If you don't like to use QueryBuilder you can set your contacts in the controller:
$oForm = $this->createForm(new CompanyForm($contacts));
and in the form you can do this:
public function __construct($contacts))
{
$this->vContacts = $contacts;
}
then:
->add('contacts', 'choice', array(
'required' => true,
'label' => 'contacts',
'choices' => $this->vContacts,
)
)
If you need to get particular set of entities in your form field, you can use query builder.
In your case (inside your form type class) it could be something like:
$builder->add('contacts', 'entity', array(
'class' => 'MainMainBundle:Contact',
'query_builder' => function (EntityRepository $er) use ($company) {
return $er->createQueryBuilder('c')
->where('c.company = :company')
->setParameter('company', $company);
},
));
Pay attention to pass $company variable.
I have a form for inserting an entity Category. This entity has two other entities that are related to it.
One related entity is an other separate Entity Group. The other entity is itself self-referenced Category that is an array collection that represents preconditions. So far so good, i can persist the main entity with the relations with the correct ORM annotations.
Rough scheme of Category
id : int
title: string
group : Group obj
preconditions : [Category obj, Category obj, ...]
I made an type class for creating the form as described as best-practice in the documentation.
$form = $this->createForm(new CategoryType($em));
Situation
Before i persist the entity, i must initialize it and set the posted datas to it. The posted related objects can’t simply setted to the persisting entity, because they have the wrong datatype. (E.g. the self-referencing collection is posted only as array with id’s, and not an array collection of the choosed items.)
So i catch this raw datas and get separatelly the related entities from the entity manager.
Goal
The inserting entity should be filled automatically with the related entities, whitout get those separately through the entity manager
Question
Is this the meaning of the form component that those related objects are not posted and made available fully? Or what im missing in my implementation?
Is there a way to do this more automated?
On the form class for the ‘preconditions’ property i had to do mapped => false otherwise i recieve an exception that a wrong type was passed. But at the end i want that the form matches all automatically through the mapping, whitout skipping a mapping, and whitout getting the related entities separately from the entity manager.
class CategoryType extends AbstractType
{
public function __construct($em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$qb = $this->em->createQueryBuilder();
$categories = $qb->select('e.id, e.title')
->from('MyvendorCoreBundle:Category', 'e')
->indexBy('e', 'e.id')
->orderBy('e.title')
->getQuery()
->getResult();
$categories_choice = array_map(function ($value) {
return $value['title'];
}, $categories);
$builder->add('title')
->add('group_Id', new GroupType($this->em))
->add('preconditions', 'choice', array(
'choices' => $categories_choice,
'multiple' => true,
'mapped' => false
))
->add('save', 'submit');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Myvendor\CoreBundle\Entity\Category'
));
}
public function getName()
{
return 'category';
}
}
Controller method
public function newAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new CategoryType($em));
// Repopulating the form after submission
$form->handleRequest($request);
// Prepare a new empty Category
$category = new Category();
if ($form->isValid()) {
/* Catch some raw datas posted from the form */
// Posted precondition category ids to get its entities more later
$precondition_category_ids = $form->get('preconditions')->getData();
// Posted group entity that have only filled the group id in the object
$group_raw = $form->get('group_Id')->getData();
// Get the explicit filled group entity throuth the posted id.
$group = $em->find('MyvendorCoreBundle:Group', $group_raw->getGroupid());
// Fill the prepaired group with the posted datas
$category->setTitle($form->get('title')->getData());
$category->setGroupId($group);
// Adding preconditions
try {
for ($i = 0; count($precondition_category_ids) > $i; $i ++) {
$precondition_category_id = $precondition_category_ids[$i];
if (0 >= $precondition_category_id) { // Retrieving id must be greater than 0
throw new \Exception('Error retrieving precondition id');
}
$precondition_category = $em->find('MyvendorCoreBundle:Category', $precondition_category_id);
if ($precondition_category instanceof Category) {
$category->addPrecondition($precondition_category);
} else {
throw new \Exception('Error retrieving precondition as Myvendor\CoreBundle\Entity\Category');
}
}
$em->persist($category); // Insert the group item with its relations
$em->flush();
echo '<h1 style="color:green">persisted</h1>';
} catch (\Exception $e) {
echo '<h1 style="color:red">' . $e->getMessage() . '</h1>';
}
}
return $this->render('MyvendorCoreBundle:fbm:new.html.twig', array(
'form' => $form->createView()
));
}
GroupType
class GroupType extends AbstractType
{
public function __construct($em){
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$groups = $this->em->createQuery("
SELECT o.groupid, o.descr
FROM MyvendorCoreBundle:Group o
INDEX BY o.groupid
ORDER BY o.descr
")->getResult();
$groups_dropdown = array();
$groups_dropdown = array_map(function($value) { return $value['descr']; }, $groups);
$builder->add('groupid', 'choice', array(
'label' => false,
'choices' => $groups_dropdown,
'attr' => array('style' => 'width: 300px')
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Myvendor\CoreBundle\Entity\Group',
));
}
public function getName()
{
return 'group';
}
}
/**
* #ORM\Entity
* #ORM\Table(name="category")
*/
class Category
{
public function __construct()
{
$this->preconditions = new ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \Myvendor\CoreBundle\Entity\Group
*
* #Assert\Type(type="Myvendor\CoreBundle\Entity\Group")
* #Assert\Valid()
* #ORM\ManyToOne(targetEntity="Myvendor\CoreBundle\Entity\Group", inversedBy="Category")
* #ORM\JoinColumn(name="group_id", nullable=false, referencedColumnName="groupid")
*/
private $group_Id;
/**
* #var string
* #Assert\NotBlank()
* #ORM\Column(type="string", length=255, nullable=false)
*/
private $title;
/**
* Preconditions are Categorys referencing to an Category.
* For a single Category its empty (which have no subelements).
* A join table holds the references of a main Category to its sub-Categorys (preconditions)
*
* #ORM\ManyToMany(targetEntity="Category")
* #ORM\JoinTable(name="category_precondition",
* joinColumns={#JoinColumn(name="category_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="category_precondition_id", referencedColumnName="id")}
* )
*/
private $preconditions;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Category
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set groupId
*
* #param \Myvendor\CoreBundle\Entity\Group $groupId
*
* #return Category
*/
public function setGroupId(\Myvendor\CoreBundle\Entity\Group $groupId)
{
$this->group_Id = $groupId;
return $this;
}
/**
* Get groupId
*
* #return \Myvendor\CoreBundle\Entity\Group
*/
public function getGroupId()
{
return $this->group_Id;
}
/**
* Add precondition
*
* #param \Myvendor\CoreBundle\Entity\Category $precondition
*
* #return $this
*/
public function addPrecondition(\Myvendor\CoreBundle\Entity\Category $precondition)
{
$this->preconditions[] = $precondition;
return $this;
}
/**
* Get preconditions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPreconditions()
{
return $this->preconditions;
}
/**
* Group
*
* #ORM\Table(name="group", indexes={#ORM\Index(name="homepage", columns={"homepage"}), #ORM\Index(name="theme", columns={"theme"})})
* #ORM\Entity
*/
class Group
{
/**
* #var string
*
* #ORM\Column(name="descr", type="string", length=60, nullable=true)
*/
private $descr;
/**
* #var integer
*
* #Assert\NotBlank()
* #ORM\Column(name="groupid", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
public $groupid;
/**
* Set descr
*
* #param string $descr
* #return Group
*/
public function setDescr($descr)
{
$this->descr = $descr;
return $this;
}
/**
* Get descr
*
* #return string
*/
public function getDescr()
{
return $this->descr;
}
/**
* Get groupid
*
* #return integer
*/
public function getGroupid()
{
return $this->groupid;
}
}
The solution was that the type of the selecting choice entities, must be not a choicelist, but really an collection type.
So use something like this
->add('preconditions', 'collection', array(
'entry_type' => 'entity',
'entry_options' => array(
'class' => 'MyVendorCoreBundle:EduStructItem',
'choice_label' => 'title'
),
'allow_add' => true,
'allow_delete' => true
))
instead of
->add('preconditions', 'choice', array(
'choices' => $categories_choice,
'multiple' => true,
'mapped' => false
))