Symfony 2 - show only one entity from OneToMany relationship - php

I need to display the min date from a group of entities in a relationship. Eg. I have User and Contracts -> the User has many Contracts, but I need to display the minimum date from contracts.
Here is the code:
class User implements UserInterface
{
/**
* #ORM\OneToMany(targetEntity="Comp\ContractBundle\Entity\Contract", mappedBy="user_id")
* #ORM\OrderBy({"id" = "DESC"})
*
*/
private $contracts;
}
class Contract
{
/**
* #var string $datastart
*
* #ORM\Column(name="datastart", type="datetime")
*/
private $datastart;
/**
* #var string $dataend
*
* #ORM\Column(name="dataend", type="datetime")
*/
private $dataend;
/**
* #var integer $user_id
*
* #ORM\ManyToOne(targetEntity="Comp\AuthBundle\Entity\User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user_id;
}
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options){
#... other data
$builder->add('contracts','collection', array(
'type' => new ContractType()
) );
}
public function getName()
{
return 'User';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Comp\AuthBundle\Entity\User',
);
}
}
And the ContractType:
class ContractType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options){
$builder->add('name','text');
$builder->add('datastart','datetime');
}
public function getName()
{
return 'Contract';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Comp\ContractBundle\Entity\Contract',
);
}
}
The problem is, I get each Entity related to the User Entity - I just need to fetch one. If you know any good example - it would be great.

Change contracts field type from collection to entity, and google for query_builder attribute of entity field type.
In that attribute you can pass a closure (returning QueryBuilder) or QueryBuilder itself, in which you can retrieve specific records from given relationship.

Related

Symfony creating choice from entity in form type

I have a lot of Categories in database.
Here is Category Entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Category")
*/
protected $rootCategory;
/**
* #ORM\Column(type="text")
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set rootCategory
*
* #param \AppBundle\Entity\Category $rootCategory
*
* #return Category
*/
public function setRootCategory(\AppBundle\Entity\Category $rootCategory = null)
{
$this->rootCategory = $rootCategory;
return $this;
}
/**
* Get rootCategory
*
* #return \AppBundle\Entity\Category
*/
public function getRootCategory()
{
return $this->rootCategory;
}
}
I want to get all categories in my edit form
EditFormType:
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use AppBundle\Controller\CategoryController;
class EditPhotoFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$categoryController = new CategoryController();
$builder->add('title', 'text');
$builder->add('description', 'textarea');
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'choices' => $categoryController->getCategories(),
));
}
public function getName()
{
return 'app_photo_edit';
}
}
getCategories()
public function getCategories() {
$em = $this->getDoctrine()->getManager();
return $em->getRepository('AppBundle:Category')->findAll();
}
I am getting next error:
Error: Call to a member function has() on null
Thats because there is not Doctrine in controller object. Where should i get Doctrine and Repository in this case?
How should i do it correct way?
First, you should NEVER instantiate any Controller class yourself. Controller classes are used by Symfony's Kernel to handle a request, and they are loaded automatically with dependencies to do so.
Right here, you don't even need to require the EntityManager in your FormType, because EntityType has a built-in option query_builder to do what you need:
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c');
},
);
This should do the trick. (check here for more details)
However, if one day you really need to import a dependancy inside your Form (whether it is EntityManager or another service), here's how you should do:
A. import the given dependency in your constructor:
private $dependency;
public function __construct(Dependency $dependency)
{
$this->$dependency = $dependency;
}
B. Declare your Form as a Service, with your dependency's id as argument:
<service id="app.form.type.edit_photo"
class="AppBundle\Form\Type\EditPhotoFormType">
<tag name="form.type" />
<argument type="service" id="app.dependencies.your_dependency" />
</service>
Then use $this->dependency in your Form wherever you need.
Hope this helps! :)

Form submit error data_class formview

I have a form builded by formbuilder
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->em->createQueryBuilder();
$query->select('sn.serial_nr')
->from('KopictAdminBundle:SerialNumber', 'sn');
$serialnumbers = $query->getQuery()->getResult();
$options = array();
foreach($serialnumbers as $serialnumber){
$options[$serialnumber['serial_nr']] = $serialnumber['serial_nr'];
}
$builder->add("serial_nr","text");
}
It shows the form correctly but when i submit it I get this error:
"The form's view data is expected to be an instance of class Kopict\AdminBundle\Entity\SerialNumber, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Kopict\AdminBundle\Entity\SerialNumber." at /var/www/kopadmin/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 373
This is how my entity looks like:
class SerialNumber
{
/**
* #var integer $id
*/
private $id;
/**
* #var interger $product_revision_id
*/
private $product_revision_id;
/**
* #var interger $booking_id
*/
private $booking_id;
/**
* #var string $serial_nr
*/
public $serial_nr;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set product_revision_id
*
* #param integer $product_revision_id
* #return SerialNumber
*/
public function setProduct_revision_id($product_revision_id)
{
$this->product_revision_id = $product_revision_id;
return $this;
}
/**
* Get product_revision_id
*
* #return integer
*/
public function getProduct_revision_id()
{
return $this->product_revision_id;
}
/**
* Set booking_id
*
* #param integer $booking_id
* #return SerialNumber
*/
public function setBooking_id($booking_id)
{
$this->booking_id = $booking_id;
return $this;
}
/**
* Get booking_id
*
* #return integer
*/
public function getBooking_id()
{
return $this->booking_id;
}
/**
* Set serial_nr
*
* #param string $serial_nr
* #return SerialNumber
*/
public function setSerial_nr($serial_nr)
{
$this->serial_nr = $serial_nr;
return $this;
}
/**
* Get serial_nr
*
* #return string
*/
public function getSerial_nr()
{
return $this->serial_nr;
}
}
I have tried to add the data_class but I can't find the good place to add it because the code keeps giving me te same error.
First of all, you need to make your serial_nr private otherwise no need to have getSerial_nr and setSerial_nr functions. Because you can reach to serial_nr outside of your class without having setters and getters.
Second, why you are adding serial numbers into options field?
Assuming you want to have serial numbers as a choice field. I have a solution for you.
Usually entities are related in Doctrine ORM as many-to-one one-to-many. In that case its very simple to get related fields as a choice field. For this case symfony has a built in entity field.
SerialNumberType - form type class. (You have to change this name to yours)
<?php
namespace Kopict\AdminBundle\Form;
use Doctrine\ORM\em;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class SerialNumberType extends AbstractType
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$query = $this->em->createQueryBuilder();
$query->select('sn.serial_nr')->from('KopictAdminBundle:SerialNumber', 'sn');
$serialNumbers = $query->getQuery()->getResult();
$choices = array();
foreach ($serialNumbers as $serialNumber) {
$choices[$serialNumber['serial_nr']] = $serialNumber['serial_nr'];
}
$builder->add("serial_nr", "choice", array(
'choices' => $choices,
));
}
public function getName()
{
return 'app_bundle_serial_number_type';
}
}
Inside Controller Action
<?php
namespace Kopict\AdminBundle\Controller;
use Kopict\AdminBundle\Entity\SerialNumber;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$serialNumber = new SerialNumber();
$form = $this->createForm($this->get('kopict_admin.form.serialnumber'), $serialNumber);
return $this->render('KopictAdminBundle:Default:index.html.twig', array('form' => $form->createView()));
}
}
services.yml
services:
kopict_admin.form.serialnumber:
class: Kopict\AdminBundle\Form\SerialNumberType
arguments: [ #doctrine.orm.entity_manager ]
scope: request

symfony : can't we have a hidden entity field?

I am rendering a form with an entity field in symfony.
It works well when i choose a regular entity field.
$builder
->add('parent','entity',array(
'class' => 'AppBundle:FoodAnalytics\Recipe',
'attr' => array(
'class' => 'hidden'
)
))
It throws the following error when I choose ->add('parent','hidden') :
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
AppBundle\Entity\FoodAnalytics\Recipe. You can avoid this error by
setting the "data_class" option to
"AppBundle\Entity\FoodAnalytics\Recipe" or by adding a view
transformer that transforms an instance of class
AppBundle\Entity\FoodAnalytics\Recipe to scalar, array or an instance
of \ArrayAccess. 500 Internal Server Error - LogicException
Can't we have hidden entity fields ?? Why not? Am I obliged to put another hidden field to retrieve the entity id?
EDIT :
Basically, what I'm trying to do is to hydrate the form before displaying it but prevent the user to change one of its fields (the parent here).
This is because I need to pass the Id as a parameter and I can't do it in the form action url.
I think you are simply confused about the field types and what they each represent.
An entity field is a type of choice field. Choice fields are meant to contain values selectable by a user in a form. When this form is rendered, Symfony will generate a list of possible choices based on the underlying class of the entity field, and the value of each choice in the list is the id of the respective entity. Once the form is submitted, Symfony will hydrate an object for you representing the selected entity. The entity field is typically used for rendering entity associations (like for example a list of roles you can select to assign to a user).
If you are simply trying to create a placeholder for an ID field of an entity, then you would use the hidden input. But this only works if the form class you are creating represents an entity (ie the form's data_class refers to an entity you have defined). The ID field will then properly map to the ID of an entity of the type defined by the form's data_class.
EDIT: One solution to your particular situation described below would be to create a new field type (let's call it EntityHidden) that extends the hidden field type but handles data transformation for converting to/from an entity/id. In this way, your form will contain the entity ID as a hidden field, but the application will have access to the entity itself once the form is submitted. The conversion, of course, is performed by the data transformer.
Here is an example of such an implementation, for posterity:
namespace My\Bundle\Form\Extension\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\DataTransformerInterface;
/**
* Entity hidden custom type class definition
*/
class EntityHiddenType extends AbstractType
{
/**
* #var DataTransformerInterface $transformer
*/
private $transformer;
/**
* Constructor
*
* #param DataTransformerInterface $transformer
*/
public function __construct(DataTransformerInterface $transformer)
{
$this->transformer = $transformer;
}
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// attach the specified model transformer for this entity list field
// this will convert data between object and string formats
$builder->addModelTransformer($this->transformer);
}
/**
* #inheritDoc
*/
public function getParent()
{
return 'hidden';
}
/**
* #inheritDoc
*/
public function getName()
{
return 'entityhidden';
}
}
Just note that in the form type class, all you have to do is assign your hidden entity to its corresponding form field property (within the form model/data class) and Symfony will generate the hidden input HTML properly with the ID of the entity as its value. Hope that helps.
Just made this on Symfony 3 and realised it's a bit different from what has already been posted here so I figured it was worth sharing.
I just made a generic data transformer that could be easily reusable across all your form types. You just have to pass in your form type and that's it. No need to create a custom form type.
First of all let's take a look at the data transformer:
<?php
namespace AppBundle\Form;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* Class EntityHiddenTransformer
*
* #package AppBundle\Form
* #author Francesco Casula <fra.casula#gmail.com>
*/
class EntityHiddenTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $objectManager;
/**
* #var string
*/
private $className;
/**
* #var string
*/
private $primaryKey;
/**
* EntityHiddenType constructor.
*
* #param ObjectManager $objectManager
* #param string $className
* #param string $primaryKey
*/
public function __construct(ObjectManager $objectManager, $className, $primaryKey)
{
$this->objectManager = $objectManager;
$this->className = $className;
$this->primaryKey = $primaryKey;
}
/**
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #return string
*/
public function getClassName()
{
return $this->className;
}
/**
* #return string
*/
public function getPrimaryKey()
{
return $this->primaryKey;
}
/**
* Transforms an object (entity) to a string (number).
*
* #param object|null $entity
*
* #return string
*/
public function transform($entity)
{
if (null === $entity) {
return '';
}
$method = 'get' . ucfirst($this->getPrimaryKey());
// Probably worth throwing an exception if the method doesn't exist
// Note: you can always use reflection to get the PK even though there's no public getter for it
return $entity->$method();
}
/**
* Transforms a string (number) to an object (entity).
*
* #param string $identifier
*
* #return object|null
* #throws TransformationFailedException if object (entity) is not found.
*/
public function reverseTransform($identifier)
{
if (!$identifier) {
return null;
}
$entity = $this->getObjectManager()
->getRepository($this->getClassName())
->find($identifier);
if (null === $entity) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'An entity with ID "%s" does not exist!',
$identifier
));
}
return $entity;
}
}
So the idea is that you call it by passing the object manager there, the entity that you want to use and then the field name to get the entity ID.
Basically like this:
new EntityHiddenTransformer(
$this->getObjectManager(),
Article::class, // in your case this would be FoodAnalytics\Recipe::class
'articleId' // I guess this for you would be recipeId?
)
Let's put it all together now. We just need the form type and a bit of YAML configuration and then we're good to go.
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Article;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class JustAFormType
*
* #package AppBundle\CmsBundle\Form
* #author Francesco Casula <fra.casula#gmail.com>
*/
class JustAFormType extends AbstractType
{
/**
* #var ObjectManager
*/
private $objectManager;
/**
* JustAFormType constructor.
*
* #param ObjectManager $objectManager
*/
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('article', HiddenType::class)
->add('save', SubmitType::class);
$builder
->get('article')
->addModelTransformer(new EntityHiddenTransformer(
$this->getObjectManager(),
Article::class,
'articleId'
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\MyEntity',
]);
}
}
And then in your services.yml file:
app.form.type.article:
class: AppBundle\Form\JustAFormType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
And in your controller:
$form = $this->createForm(JustAFormType::class, new MyEntity());
$form->handleRequest($request);
That's it :-)
With Symfony 5, I use the solution of a Hidden type that implements DataTransformerInterface interface.
<?php
namespace App\Form\Type;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Defines the custom form field type used to add a hidden entity
*
* See https://symfony.com/doc/current/form/create_custom_field_type.html
*/
class EntityHiddenType extends HiddenType implements DataTransformerInterface
{
/** #var ManagerRegistry $dm */
private $dm;
/** #var string $entityClass */
private $entityClass;
/**
*
* #param ManagerRegistry $doctrine
*/
public function __construct(ManagerRegistry $doctrine)
{
$this->dm = $doctrine;
}
/**
*
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Set class, eg: App\Entity\RuleSet
$this->entityClass = sprintf('App\Entity\%s', ucfirst($builder->getName()));
$builder->addModelTransformer($this);
}
public function transform($data): string
{
// Modified from comments to use instanceof so that base classes or interfaces can be specified
if (null === $data || !$data instanceof $this->entityClass) {
return '';
}
$res = $data->getId();
return $res;
}
public function reverseTransform($data)
{
if (!$data) {
return null;
}
$res = null;
try {
$rep = $this->dm->getRepository($this->entityClass);
$res = $rep->findOneBy(array(
"id" => $data
));
}
catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage());
}
if ($res === null) {
throw new TransformationFailedException(sprintf('A %s with id "%s" does not exist!', $this->entityClass, $data));
}
return $res;
}
}
And to use the field in the form:
use App\Form\Type\EntityHiddenType;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Field name must match entity class, eg 'ruleSet' for App\Entity\RuleSet
$builder->add('ruleSet', EntityHiddenType::class);
}
This can be achieved fairly cleanly with form theming, using the standard hidden field theme in place of that for the entity. I think using transformers is probably overkill, given that hidden and select fields will give the same format.
{% block _recipe_parent_widget %}
{%- set type = 'hidden' -%}
{{ block('form_widget_simple') }}
{% endblock %}
A quick solution whitout creating new transformer and type classes. When you want to prepopulate an related entity from the db.
// Hidden selected single group
$builder->add('idGroup', 'entity', array(
'label' => false,
'class' => 'MyVendorCoreBundle:Group',
'query_builder' => function (EntityRepository $er) {
$qb = $er->createQueryBuilder('c');
return $qb->where($qb->expr()->eq('c.groupid', $this->groupId()));
},
'attr' => array(
'class' => 'hidden'
)
));
This results a single hidden selection like:
<select id="mytool_idGroup" name="mytool[idGroup]" class="hidden">
<option value="1">MyGroup</option>
</select>
But yes, i agree that with a little more effort by using a DataTransformer you can achieve something like:
<input type="hidden" value="1" id="mytool_idGroup" name="mytool[idGroup]"/>
That will do what you need:
$builder->add('parent', 'hidden', array('property_path' => 'parent.id'));
one advantage of using transformer with text/hidden field over entity type field is that the entity field preloads choices.
<?php declare(strict_types=1);
namespace App\Form\DataTransformer;
use Doctrine\Persistence\ObjectRepository;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class EntityIdTransformer implements DataTransformerInterface
{
private \Closure $getter;
private \Closure $loader;
public function __construct(
private readonly ObjectRepository $repository,
\Closure|string $getter,
\Closure|string $loader)
{
$this->getter = is_string($getter) ? fn($e) => $e->{'get'.ucfirst($getter)}() : $getter;
$this->loader = is_string($loader) ? fn($id) => $this->repository->findOneBy([$loader => $id]) : $loader;
}
/**
* Transforms an object (entity) to a string|number.
*
* #param object|null $entity
* #return string|int|null
*/
public function transform(mixed $entity): string|int|null
{
if (empty($entity)) {
return null;
}
return $this->getIdentifier($entity);
}
/**
* Transforms a string|number to an object (entity).
*
* #throws TransformationFailedException if entity is not found
* #param string|int $identifier
* #return object|null
*/
public function reverseTransform(mixed $identifier): object|null
{
if (empty($identifier)) {
return null;
}
//TODO: is this needed?
if (is_object($identifier)) {
$identifier = $this->transform($identifier);
}
$entity = $this->fetchEntity($identifier);
if (null === $entity) {
throw new TransformationFailedException(sprintf(
'An entity with ID "%s" does not exist!',
$identifier
));
}
return $entity;
}
protected function getIdentifier(object $entity): int|string
{
$getter = $this->getter;
return $getter($entity);
}
protected function fetchEntity(int|string $identifier): object|null
{
$loader = $this->loader;
return $loader($identifier, $this->repository);
}
}
can be used like
$builder
->add('parent', FormType\TextType::class, [
'label' => 'Parent',
])->get('parent')->addModelTransformer(new EntityIdTransformer(
repository: $this->em->getRepository($options['data_class']),
getter: 'id',
loader: 'id',
));
or
$builder
->add('parent', FormType\TextType::class, [
'label' => 'Parent',
])->get('parent')->addModelTransformer(new EntityIdTransformer(
repository: $this->em->getRepository($options['data_class']),
getter: fn($e) => $e->getHash(),
loader: fn($id, $repo) => $repo->findOneBy(['hash' => $id])
));
or even
$builder
->add('parent', FormType\TextType::class, [
'label' => 'Parent',
])->get('parent')->addModelTransformer(new EntityIdTransformer(
repository: $this->em->getRepository($options['data_class']),
getter: 'hash',
loader: fn($id, $repo) => $repo->createQueryBuilder('e')->andWhere('e.hash = :hash')->andWhere('e.disabled = 0')->setParameter('hash', $id)->getQuery()->getOneOrNullResult()
));

Managing users/roles/groups in FOSUserBundle

I am developing a simple CRUD to manage users/roles/groups of the application in which I am working. To manage users I'm using FOSUserBundle. What I want to do can be accomplished in several ways:
Assigning roles to groups and then assign users to these groups
Assigning roles to users directly
But I have no idea how. I knew that FOSUser BaseUser class already has a column roles and in the documentation of FOSUser explains how to establish a ManyToMany relationship between users and groups but do not talk anything about roles. The only idea that comes to mind is to create an entity to manage the roles as well as a form for the same purpose, something like what you see below:
Role Entity
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="fos_role")
* #ORM\Entity(repositoryClass="UserBundle\Entity\Repository\RoleRepository")
*
* #see User
* #see \UserBundle\Role\RoleHierarchy
*
*/
class Role implements RoleInterface
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=80, unique=true)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Role", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
* #var Role[]
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Role", mappedBy="parent")
* #var ArrayCollection|Role[]
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;
public function __construct($role = "")
{
if (0 !== strlen($role)) {
$this->name = strtoupper($role);
}
$this->users = new ArrayCollection();
$this->children = new ArrayCollection();
}
/**
* #see RoleInterface
*/
public function getRole()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getUsers()
{
return $this->users;
}
public function addUser($user, $addRoleToUser = true)
{
$this->users->add($user);
$addRoleToUser && $user->addRole($this, false);
}
public function removeUser($user)
{
$this->users->removeElement($user);
}
public function getChildren()
{
return $this->children;
}
public function addChildren(Role $child, $setParentToChild = true)
{
$this->children->add($child);
$setParentToChild && $child->setParent($this, false);
}
public function getDescendant(& $descendants = array())
{
foreach ($this->children as $role) {
$descendants[spl_object_hash($role)] = $role;
$role->getDescendant($descendants);
}
return $descendants;
}
public function removeChildren(Role $children)
{
$this->children->removeElement($children);
}
public function getParent()
{
return $this->parent;
}
public function setParent(Role $parent, $addChildToParent = true)
{
$addChildToParent && $parent->addChildren($this, false);
$this->parent = $parent;
}
public function __toString()
{
if ($this->children->count()) {
$childNameList = array();
foreach ($this->children as $child) {
$childNameList[] = $child->getName();
}
return sprintf('%s [%s]', $this->name, implode(', ', $childNameList));
}
return sprintf('%s', $this->name);
}
}
Role Form Type
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RoleType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('parent');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\UserBundle\Entity\Role'
));
}
/**
* #return string
*/
public function getName()
{
return 'role';
}
}
If so what would add to my user form would look something like this
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', 'text')
->add('email', 'email')
->add('enabled', null, array(
'label' => 'Habilitado',
'required' => false
))
->add('rolesCollection', 'entity', array(
'class' => 'UserBundle:Role',
'multiple' => true,
'expanded' => true,
'attr' => array('class' => 'single-line-checks')
))
->add('groups', 'entity', array(
'class' => 'UserBundle:Group',
'multiple' => true,
'expanded' => true,
));
}
But I do not know if it is the right way to handle the roles since in this case would be creating a new table in my DB called fos_roles where were the relationships between users/roles is handled but relationships between groups/roles stay out of it, then that's where I'm a little lost and need help from the more experienced in that tell me and alert if I'm on track and that would make them to achieve what I explain in the first two points. Any advice or help? How do you deal with this?
Symfony Roles
The way FOSUserBundle deals with Roles is to store them in the roles column that you've seen, in a serialised format like this: a:1:{i:0;s:10:"ROLE_ADMIN";}. So there's no need for any other tables or entities^.
^ This is in contrast to Groups, which need to be explicitly configured, are represented by a separate Table/Entity, and do involve relating Users to Groups in the DB. Groups let you define arbitrary collections of Roles which can then be given to each User as a discrete bundle.
A User can be a member of any number of Roles. They're identified by strings starting with "ROLE_", you can just start using a new Role.
What the Roles mean for your application is completely up to you, but they're quite a high-level tool - a User is either in a particular Role or they aren't.
You put people in Roles either via the Symfony console:
php app/console fos:user:promote testuser ROLE_ADMIN
Or in PHP:
$user = $this->getUser();
$userManager = $container->get('fos_user.user_manager');
$user->addRole('ROLE_ADMIN');
$userManager->updateUser($user);
And you can test membership in PHP:
$user = $this->getUser();
if ($user->hasRole('ROLE_ADMIN'))
{
//do something
}
Or using Annotations:
/**
* #Security("has_role('ROLE_ADMIN')")
*/
public function adminAction()
{
//...
or
/**
* #Security("has_role('ROLE_ADMIN')")
*/
class AdminController
{
//...
I added the functionality to add default group to the user during registration by overriding the confirmAction in Registration Controller
What I did is I overrided the Registration Controller in my project Bundle by defining the parent to FosUserBUndle .
Then created a function confirmedAction and in the body of the function added this code
$repository = $em->getRepository('AdminAdminBundle:Group');
$group = $repository->findOneByName('staff');
$em = $this->getDoctrine()->getEntityManager();
$user = $this->getUser();
$user->addGroup($group);
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
if (!is_object($user) || !$user instanceof FOS\UserBundle\Model\UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('FOSUserBundle:Registration:confirmed.html.twig',
['user' => $user]);
And it perfectly saved in db with group assignment.
Hope this will help some one in need as there is little information about the implementation in official fosuserbundle doc.

Symfony2 Registration form with only single record of collection field

So I'm trying to create a registration form in Symfony 2 which contains my "Person" entity. The person entity has a one-to-many join, and I want the registration form to allow the user to select a single instance of this "Many" side of the join.
The structure is Users and Institutions. A user can have many institutions. I want a user to select a single institution at registration time (but the model allows for more later).
The basic structure is:
RegistrationType -> PersonType -> PersonInstitutionType
…with corresponding models:
Registration (simple model) -> Person (doctrine entity) -> PersonInstitution (doctrine entity, oneToMany relation from Person)
I tried to pre-populate an empty Person & PersonInstitution record in the RegistrationController but it gives me the error:
Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "TB\CtoBundle\Entity\PersonInstitution" given
(ok above has been fixed).
I've moved the code from my website to here below, trying to remove all the irrelevant bits.
src/TB/CtoBundle/Form/Model/Registration.php
namespace TB\CtoBundle\Form\Model;
use TB\CtoBundle\Entity\Person;
class Registration
{
/**
* #var Person
*/
private $person
private $termsAccepted;
}
src/TB/CtoBundle/Form/RegistrationType.php
namespace TB\CtoBundle\Form;
use TB\CtoBundle\Form\PersonType;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('person', new PersonType());
$builder->add('termsAccepted','checkbox');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Form\Model\Registration',
'cascade_validation' => true,
));
}
public function getName()
{
return 'registration';
}
}
src/TB/CtoBundle/Entity/Person.php
namespace TB\CtoBundle\Entity;
use TB\CtoBundle\Entity\PersonInstitution
/**
* #ORM\Entity()
*/
class Person
{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="PersonInstitution", mappedBy="person", cascade={"persist"})
*/
private $institutions;
}
src/TB/CtoBundle/Form/PersonType.php
namespace TB\CtoBundle\Form;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('institutions', 'collection', array('type' => new PersonInstitutionType()))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Entity\Person',
));
}
/**
* #return string
*/
public function getName()
{
return 'tb_ctobundle_person';
}
}
src/TB/CtoBundle/Entity/PersonInstitution.php
namespace TB\CtoBundle\Entity
/**
* PersonInstitution
*
* #ORM\Table()
* #ORM\Entity
*/
class PersonInstitution
{
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="institutions", cascade={"persist"})
*/
private $person;
/**
* #ORM\ManyToOne(targetEntity="Institution", inversedBy="members")
*/
private $institution;
/**
* #ORM\Column(type="boolean")
*/
private $approved;
}
src/TB/CtoBundle/Form/PersonInstititionType.php
namespace TB\CtoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PersonInstitutionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('approved')
->add('person')
->add('institution')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TB\CtoBundle\Entity\PersonInstitution'
));
}
/**
* #return string
*/
public function getName()
{
return 'tb_ctobundle_personinstitution';
}
}
src/TB/CtoBundle/Controller/Registration.php
namespace TB\CtoBundle\Controller;
class RegisterController extends Controller
{
/**
*
* #param Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function registerAction(Request $request)
{
$registration = new Registration;
$person = new Person();
$institution = new PersonInstitution();
$person->addInstitution($institution);
$registration->setPerson($person);
// this causes error:
// Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
// $institution->setPerson($person);
$form = $this->createForm(new RegistrationType(), $registration);
$form->handleRequest($request);
if($form->isValid()) {
$registration = $form->getData();
$person = $registration->getPerson();
// new registration - account status is "pending"
$person->setAccountStatus("P");
// I'd like to get rid of this if possible
// for each "PersonInstitution" record, set the 'person' value
foreach($person->getInstitutions() as $rec) {
$rec->setPerson($person);
}
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
}
return $this->render('TBCtoBundle:Register:register.html.twig', array('form' => $form->createView()));
}
}
Here is a detailed solution for adding an Collection field to Person entity and formType.
Your complex question with Registration entity can be solved with this.
I suggest you to use this 3 entity related connection if it is really needed. (only because of termsAccepted data!?)
If you won't change your opinion, then use this annotation:
Registration code:
use TB\CtoBundle\Entity\Person;
/**
* #ORM\OneToOne(targetEntity="Person")
* #var Person
*/
protected $person;
Person code:
use TB\CtoBundle\Entity\PersonInstitution;
/**
* #ORM\OneToMany(targetEntity="PersonInstitution", mappedBy = "person")
* #var ArrayCollection
*/
private $institutions;
/* I suggest you to define these functions:
setInstitutions(ArrayCollection $institutions),
getInstitutions()
addInstitution(PersonInstitution $institution)
removeInstitution(PersonInstitution $institution)
*/
PersonInstitution code:
use TB\CtoBundle\Entity\Person;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="institutions", cascade={"persist"}))
* #var Person
*/
private $person;
PersonType code:
use TB\CtoBundle\Form\PersonInstitutionType;
->add('institutions', 'collection', array(
'type' => new PersonInstitutionType(), // here is your mistake!
// Other options can be selected here.
//'allow_add' => TRUE,
//'allow_delete' => TRUE,
//'prototype' => TRUE,
//'by_reference' => FALSE,
));
PersonController code:
use TB\CtoBundle\Entity\Person;
use TB\CtoBundle\Entity\PersonInstitution;
/**
* ...
*/
public funtcion newAction()
{
$person = new Person;
$institution = new PersonInstitution;
$institution->setPerson($person);
$person->addInstitution($institution);
$form = $this->createForm(new PersonType($), $person); // you can use formFactory too.
// If institution field is required, then you have to check,
// that is there any institution able to chose in the form by the user.
// Might you can redirect to institution newAction in that case.
return array( '...' => $others, 'form' => $form);
}
If you need more help in twig code, then ask for it.

Categories