I am stuck and frustrated with the bellow error message:
Catchable Fatal Error: Argument 1 passed to Medicine\UserBundle\Entity\User
::setUsertype() must be an instance of Medicine\UserBundle\Entity\Usertype,
instance of Doctrine\Common\Collections\ArrayCollection given, called in
/opt/lampp/htdocs/drugs/vendor/symfony/src/Symfony/Component/Form/Util
/PropertyPath.php on line 347 and defined in /opt/lampp/htdocs/drugs/src/
Medicine/UserBundle/Entity/User.php line 224
What I think this error is due to use of manytoone field in my entity, I even tried with keeping onetomany in another entity.
I have a user entity and a usertype entity, the usertype_id is a manytoone field in user table. here is the code for both the entities:-
User
namespace Medicine\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="Medicine\UserBundle\Repository\UserRepository")
* #ORM\Table(name="user")
* #ORM\HasLifecycleCallbacks()
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\HasLifecycleCallbacks()
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $username;
/**
* #ORM\ManyToOne(targetEntity="Usertype", inversedBy="users")
* #ORM\JoinColumn(name="usertype_id", referencedColumnName="id")
*/
protected $usertype;
/**
* #ORM\Column(type="string")
*/
protected $image;
/**
* Set usertype
*
* #param Medicine\UserBundle\Entity\Usertype $usertype
*/
public function setUsertype(\Medicine\UserBundle\Entity\Usertype $usertype)
{
$this->usertype = $usertype;
}
/**
* Get usertype
*
* #return Medicine\UserBundle\Entity\Usertype
*/
public function getUsertype()
{
return $this->usertype;
}
}
I am just showing the concerned code, i have all the getter and setter methods for the above code.
UserType
namespace Medicine\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="Medicine\UserBundle\Repository\UsertypeRepository")
* #ORM\Table(name="usertype")
* #ORM\HasLifecycleCallbacks()
*/
class Usertype
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\HasLifecycleCallbacks()
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="usertype")
*/
protected $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add users
*
* #param Medicine\UserBundle\Entity\User $users
*/
public function addUser(\Medicine\UserBundle\Entity\User $users)
{
$this->users[] = $users;
}
/**
* Get users
*
* #return Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
}
Controller
This Executes when a user wants to login. He will fill in the username password and a UserType:
public function indexAction()
{
$entity = new User();
$form = $this->createForm(new LoginForm(), $entity);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
echo "<pre>"; print_r($entity->getUsertype()); exit;
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($entity);
$userrepository = $em->getRepository('MedicineUserBundle:User');
echo "<pre>"; print_r($entity->getUsertype()); exit;
$all = $userrepository->findOneBy(array('login' => $entity->getLogin(), 'password' => $entity->getPassword()));
if($all)
{
return $this->redirect($this->generateUrl('MedicineUserBundle_login'));
}
}
}
return $this->render('MedicineUserBundle:User:loginpage.html.twig',array(
'form' => $form->createView()
));
}
Login Form
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('login', 'text', array('label' => 'Username',))
->add('password','password')
->add('usertype', 'entity', array('class' => 'MedicineUserBundle:Usertype', 'property'=>'name', 'multiple' => true, ))
;
}
The 'multiple' => true in combination with your entity association definition is causing this problem.
You should find that if you change multiple to false (and as such can only select one UserType for your User), things work properly.
If you want multiple UserTypes for one User, you have a Many-to-Many association - one user can have many UserTypes, and one UserType can have many Users. See Doctrine's ManyToMany association type to implement this. Documentation Here.
Hope this helps.
Related
I'm trying to get the "demands" of a user.
User can have some demands and a demand have only one user (OneToMany)
This is my User entity (Utilisateur in french) :
class Utilisateur extends AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="ulid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UlidGenerator::class)
*/
private Ulid $id;
/**
* #ORM\OneToMany(targetEntity=DemandeTransport::class, mappedBy="utilisateur", orphanRemoval=true)
*/
private Collection $demandeTransports;
And my demands entity :
class DemandeTransport extends AbstractEntity
{
/**
* #ORM\Id
* #ORM\Column(type="ulid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UlidGenerator::class)
*/
private Ulid $id;
/**
* #ORM\ManyToOne(targetEntity=Utilisateur::class, inversedBy="demandeTransports")
* #ORM\JoinColumn(nullable=false)
*/
private Utilisateur $utilisateur;
My controller receiving the request :
/**
* #throws Exception
*/
#[Route('/liste_propositions_transporteur', name: 'liste_propositions_transporteur', methods: ['GET'])]
public function listePropositionsTransporteur(Request $request): Response
{
return match ($request->getMethod()) {
'GET' => new Response(json_encode(['success' => true, 'data' => $this->propositionsTransportService->handleListePropositionsByUser($this->getUser())])),
default => new Response(404),
};
}
The service handling the request and retreiving the demands :
/**
* #param UserInterface $user
* #return array
*/
public function handleListePropositionsByUser(UserInterface $user) : array
{
$propositions = [];
foreach ($this->propositionTransportRepository->findPropositionsByUtilisateur($user) as $propositionTransport) {
$propositions[] = DemandeTransportHelper::serializePropositionDemande($propositionTransport);
}
return $propositions;
}
And the DQL :
/**
* #param UserInterface $user
* #return mixed
*/
public function findPropositionsByUtilisateur(UserInterface $user) : mixed
{
$q = $this->createQueryBuilder('p')
->where('p.utilisateur = :utilisateur')
->setParameters([
'utilisateur' => $user
])
->orderBy('p.dateCreation', 'DESC');
return $q->getQuery()->getResult();
}
So :
When i'm doing $utilisateur->getDemandesTransports() : it works by showing me all the demands.
Well, but when I'm trying to get them by DQL (cause I want them orderd by), it returns me 0 results...
Solved by setting the parameter type :
->setParameter('utilisateur', $utilisateur->getId(), 'ulid')
I'm using ULID (UUID like) on IDs.
https://symfony.com/doc/current/components/uid.html#working-with-ulids
With annotations
You can order your data by specifying sorting in your $demandeTransports property annotations.
/**
* #ORM\OneToMany(targetEntity=DemandeTransport::class, mappedBy="utilisateur", orphanRemoval=true)
* #ORM\OrderBy({"dateCreation" = "DESC"})
*/
private Collection $demandeTransports;
So when you call $utilisateur->getDemandesTransports() you will get ordered data.
With DQL
Also if you still want to use DQL then you should change your query to this as you need to join the Utilisateur entity then you can use the desired properties
$q = $this->createQueryBuilder('p')
->join('p.utilisateur', 'u')
->where('u.utilisateur = :utilisateur')
->setParameters([
'utilisateur' => $user
])
->orderBy('u.dateCreation', 'DESC');
it's not a typical approach where "user adds article". This approach is a try to build nicely configurable, and data validated (by forms on frontend) table in database contains data based from other tables - something I'm trying to call "Dictonary tables", and store ID in main table of of vchar representation in other tables
This approach gives me a table which is very fast to process data on it.
Unfortunately, I encountered some difficulties using EntityType in forms
Adding ORM relation to entity (ConfigTable.php) causes writing null into database (POST have all values properly seted)
UPDATE 2018.02.15
annotation
#ORM\ManyToOne(targetEntity="AppBundle\Entity\yourentity", inversedBy="mappedByFieldName")
#ORM\JoinColumn(name="field_in_current_entity_declared_as_ORM#/Column")
protected $entityOne
#ORM\JoinColum is nt working properly here. Setting "name" attribute pointing to existing field can't create proper foreign keys.
If JoinColumn is not setted at all - FK are pointing to your current annotated field wits suffix _id
ie entity_one_id
and variables are saved from form,
but if in JoinColumn you set *name="pointing_to_field_annotaded_as_column_for_doctrine"*
during saving data to database you will get error of that object
*YourBundle/Entity/EntityName - cannot be converted to INT*
DefaultController.php
/**
* #param Request $request
* #Route("/add", name="action_configtable_add")
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function addFieldConfigTableAction(Request $request)
{
$form = $this->createForm(ConfigTableFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($data);
$em->flush();
return $this->redirectToRoute('homepage');
}
return $this->render(
':Forms/ConfigTable:add.config.table.item.html.twig',
[
'cdata' => $form->createView(),
]
);
}
MainEntity - in which I would like to store ID from dictonary entities (DictonaryOne.php, OtherDictonary.php)
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ConfigTable
* #package AppBundle\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\ConfigTableRepository")
* #ORM\Table(name="config_table")
*/
class ConfigTable
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/protected $id;
/**
* #ORM\Column(type="integer", nullable=true )
*/protected $dictonaryOne;
/**
* #ORM\Column(type="integer", nullable=true )
*/protected $dictonaryTwo;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\DictonaryOne", inversedBy="configTableOne", cascade={"persist"})
* #ORM\JoinColumn(name="dictonary_one")
*/
protected $dictOne;
//usual getters & setters
}
DictonaryOne Entity (binded by relation, cant save it's id into database in ConfigTable)
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Class DictonaryOne
* #package AppBundle\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\DictonaryOneRepository")
* #ORM\Table(name="dictonary_one")
*/
class DictonaryOne
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/protected $id;
/**
* #var string
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
* #var string
* #ORM\Column(type="string", nullable=true)
*/
protected $description;
/**
* #var boolean
* #ORM\Column(type="boolean", options={"default":1})
*/
protected $isActive;
/**
* #var int
* #ORM\Column(type="integer", options={"default":"1"})
*/
protected $orderField;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\ConfigTable", mappedBy="dictOne")
*/
protected $configTableOne;
public function __construct()
{
$this->configTableOne = new ArrayCollection();
}
//usual getters & setters and:
/**
* #return mixed
*/
public function getConfigTableOne()
{
return $this->configTableOne;
}
/**
* #param mixed $configTableOne
*/
public function setConfigTableOne(ConfigTable $configTableOne)
{
$this->configTableOne = $configTableOne;
}
}
OtherDictonary Entity: (not binded by relation, data are stored in database corectly after submit)
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class OtherDictonary
* #package AppBundle\Entity
* #ORM\Entity()
* #ORM\Table(name="other_dictonary")
*/
class OtherDictonary
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/protected $id;
/**
* #var string
* #ORM\Column(type="string")
*/
protected $name;
/**
* #var string
* #ORM\Column(type="string")
*/
protected $description;
/**
* #var boolean
* #ORM\Column(type="boolean")
*/
protected $isActive;
/**
* #var int
* #ORM\Column(type="integer")
*/
protected $orderField;
//usual getters & setters
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
public function __toString()
{
return (string)$this->id;
}
}
ConfigTableFormType.php
namespace AppBundle\Form;
use AppBundle\Entity\DictonaryOne;
use AppBundle\Entity\OtherDictonary;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ConfigTableFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'dictonaryOne',
EntityType::class,
[
'class'=> DictonaryOne::class,
'label' => 'label from dictonary 1',
'choice_value' => 'id',
'choice_label' => 'description',
'expanded' => false,
'multiple' => false,
// 'mapped' => false, //not working
]
)
->add(
'dictonaryTwo',
EntityType::class,
[
'class' => OtherDictonary::class,
'label' => 'label from other dictonary ',
'choice_value' => 'id',
'choice_label' => 'description',
'query_builder' => function (\Doctrine\ORM\EntityRepository $er) {
return $er->createQueryBuilder('d2')
->andWhere('d2.isActive = 1')
->orderBy('d2.description', 'ASC');
},
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'AppBundle\Entity\ConfigTable',
]
);
}
public function getBlockPrefix()
{
return 'app_bundle_config_table_form_type';
}
}
in ORM annotation, reference JoinColumn(name="xxx") is pointless.
try don't use notation:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\DictonaryOne", inversedBy="configTableOne", cascade={"persist"})
* #ORM\JoinColumn(name="dictonary_one")
*/
protected $dictOne;
use:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\DictonaryOne", inversedBy="configTableOne", cascade={"persist"})
*/
protected $dictOne;
foreign keys are formed well by symfony. Checked versions:
Symfony 2.8 - 3.3.16,
"doctrine/doctrine-bundle": "^1.6", (1.8.1)
"doctrine/orm": "^2.5", (v2.5.14)
i'm newbie in symfony and I'm trying to implement a Data Transformer. I followed the documentation and looked different solutions posted in here. But I haven't found what is wrong with my code.
I'm getting this error:
Catchable Fatal Error: Object of class AppBundle\Entity\Todo could not be converted to string
If someone knows another post with the solution, please tell me know where I could look for it.
Thank you in advance.
So, these are my Entities. Todo class
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Todo
*
* #ORM\Table(name="todo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TodoRepository")
*/
class Todo
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=20)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="category", type="string", length=20)
*/
private $category;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=10)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="priority", type="string", length=10)
*/
private $priority;
/**
*
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="todos"
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $creatorId;
//... getters and setters
User class:
<?php
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #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();
// your own logic
}
/**
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\Todo",
* mappedBy="creatorId"
* )
*/
private $todos;
}
In the User class, I don't have getter/setters
My TodoType
class TodoType extends AbstractType{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
'label' => 'Name',
'required' => true
)
)
->add('category', TextType::class, array(
'label' => 'Category'
)
)
->add('priority', EntityType::class, array(
'class' => 'AppBundle:Todo',
'choice_label' => 'priority',
)
)
->add('creatorId', TextType::class, array(
'label' => 'creator Id:',
));
$builder->get('creatorId')
->addModelTransformer(new IssueToNumberTransformer($this->manager));
}
public function getName()
{
return 'todo';
}
}
Transformer
<?php
namespace FOS\UserBundle\Form\DataTransformer;
use AppBundle\Entity\User;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class IssueToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
/**
* Transforms an object (creatorId) to a string (number).
*
* #param creatorId|null $creatorId
* #return string
*/
public function transform($creatorId)
{
if (null === $creatorId) {
return '';
}
return $creatorId->getId();
}
/**
* Transforms a string (number) to an object (creatorId).
*
* #param string $creatorId
* #return creatorId|null
* #throws TransformationFailedException if object (creatorId) is not found.
*/
public function reverseTransform($creatorId)
{
// no issue number? It's optional, so that's ok
if (!$creatorId) {
return;
}
$creatorId = $this->manager
->getRepository('AppBundle:User')
// query for the issue with this id
->find($creatorId);
if (null === $creatorId) {
throw new TransformationFailedException(sprintf(
'A user with number "%s" does not exist!',
$creatorId
));
}
return $creatorId;
}
}
Controller (function)
public function createAction(Request $request){
$em = $this->getDoctrine()->getManager();
// 1) build the form
$todo = new Todo();
$form = $this->createForm(new TodoType($em), $todo);
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 3) save the Todo!
$em->persist($todo);
$em->flush();
return $this->redirectToRoute('todo_create');
}
return $this->render('todo/create.html.twig', array(
'todo'=>$todo,
'form'=>$form->createView()
));
}
It's at this part:
return $this->render('todo/create.html.twig', array(
'todo'=>$todo,
'form'=>$form->createView()
));
Because you are trying to pass the $todo object to your Twig template. You can't do that. What do you need? If you just need the Todo name and you have created getters/setter, you could instead do like this:
return $this->render('todo/create.html.twig', array(
'todo_name' => $todo->getName(),
'form' => $form->createView()
));
Hopefully that makes sense.
EDIT #2 based on comments.
Also this line is wrong in your controller:
$form = $this->createForm(new TodoType($em), $todo);
Should be like this:
$form = $this->createForm(TodoType::class, $todo);
The disclaimer: I'm new to Symfony. Really struggling with the collection field type and simple setup of a OnetoOne relationship.
Scenario: I have a Product entity class and a Category entity class. I am using a collection field on the product to create Category items. I am picturing a separate Category Table with a Name column and a related product_id column. I know it's not typically practical. For arguments sake Category may as well be Feature or something else as I just want to establish the relationship as a scenario to extend. Ultimately Category will become an Image field allowing me to pull related images into a view.
The Problem: I've followed the cookbook article (http://symfony.com/doc/current/cookbook/form/form_collections.html) over and over but just hitting brick wall. I'm on board with the principle but feel like I'm missing something significant. I've got a protoype form field generating from the javascript and I'm successfully persisting/saving new Products (in full) and new Categories (only in part). The related product id is not being written into the join column.
I'm sure it's a case of getters/setters not being taken care of correctly. I' relying on doctrine to generate them automatically. Or the issue may be with some unspecified requirement to set the id to the Category in the controller.
Code to follow. Help greatly appreciated as been banging this around for a couple of days and getting nowhere fast. Really frustrating as grasped all other principles really quickly and really chuffed with building a first project in symfony.
Product Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\Category;
/**
* Page
*
* #ORM\Table(name="product")
* #ORM\Entity
* #Vich\Uploadable
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\OnetoOne(targetEntity="Category", cascade={"persist"})
*/
protected $categorys;
public function __construct()
{
$this->categorys = new ArrayCollection();
}
}
Category Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Product;
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="Product", cascade={"persist"})
*/
protected $product;
/**
* 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 product
*
* #param \AppBundle\Entity\Product $product
* #return Category
*/
public function setProduct(\AppBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
Product Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Product;
use AppBundle\Entity\Category;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('categorys', 'collection', array(
'type' => new CategoryType(),
'allow_add' => true,
'by_reference' => false,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-default'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}
public function getName()
{
return 'product';
}
}
Category Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Category;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Category',
));
}
public function getName()
{
return 'category';
}
}
Product Controller: New Product
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
$category->getProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
I bet that you don't need OneToOne but OneToMany relation for Product vs Category. Because i think that one category can have multiple products but a product can only have one category.
Another approach is even to make a ManyToMany relation. In that case each category can have multiple products, but each product can be in multiple categories too.
OneToOne is not often used, only in special circumstances.
I think you dont need the collection-type too in your case. You could change this for the entity type. Just make the entities ManyToOne as i explained.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*/
private $description;
/**
* #var float
*
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
**/
private $category;
}
and category:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=64)
*/
private $name;
public function __toString() {
return $this->name;
}
}
and now use the console to run some commands:
app/console doctrine:generate:entities AppBundle
app/console doctrine:schema:update --force
app/console doctrine:generate:crud AppBundle:Product
app/console doctrine:generate:crud AppBundle:Category
choose YES if asked if you want to generate write Actions
Add a __toString() method to your Category entity:
public function __toString() {
return $this->name;
}
Now see your new controllers and routes and try them.
To see all your routes:
app/console router:debug
You must have this in your app/config/routing.yml:
app:
resource: "#AppBundle/Controller/"
type: annotation
At the end you will have:
- two new entities
- two new controllers
- two new form types
- eight new views
You can learn a lot from the created code and change everything as you wish.
Good luck
I know it's a old question, but :
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
#################################
##$category->getProduct($this);##
#################################
$category->setProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
You call getProduct($this) instead of setProduct($this)
I'm trying to create a ManyToMany relationship between Sonata's User and an entity called "Network" that resides in my bundle.
The idea is to be able to assign multiple Networks to each user via the User creation/edition of SonataAdmin.
Here's what I've got:
User Entity:
<?php
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class User extends BaseUser
{
/**
* #var integer $id
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="\Acme\TestBundle\Entity\Network", mappedBy="users", cascade={"persist", "remove"})
*/
protected $networks;
public function __construct()
{
parent::__construct();
$this->networks = new ArrayCollection();
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Add network
*
* #param \Acme\TestBundle\Entity\Network $network
* #return User
*/
public function addNetwork($network)
{
$this->networks[] = $network;
$network->addUser($this);
return $this;
}
/**
* Remove network
*
* #param \Acme\TestBundle\Entity\Network $network
*/
public function removeNetwork($network)
{
$this->networks->removeElement($network);
$network->removeUser($this);
}
/**
* Get networks
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getNetworks()
{
return $this->networks;
}
}
Network Entity:
<?php
namespace Acme\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class Network
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="\Application\Sonata\UserBundle\Entity\User", inversedBy="networks")
* #ORM\JoinTable(name="Networks_Users")
*/
protected $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function __toString()
{
return $this->getName();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Network
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add user
*
* #param \Application\Sonata\UserBundle\Entity\User $user
* #return Network
*/
public function addUser($user)
{
$this->users[] = $user;
return $this;
}
/**
* Remove user
*
* #param \Application\Sonata\UserBundle\Entity\User $user
*/
public function removeUser($user)
{
$this->users->removeElement($user);
}
/**
* Get users
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getUsers()
{
return $this->users;
}
}
app\config.yml
sonata_user:
admin:
user:
class: Acme\TestBundle\Admin\UserAdmin
#...#
User Admin class:
<?php
namespace Acme\TestBundle\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as SonataUserAdmin;
class UserAdmin extends SonataUserAdmin
{
/**
* {#inheritdoc}
*/
protected function configureFormFields(\Sonata\AdminBundle\Form\FormMapper $formMapper)
{
parent::configureFormFields($formMapper);
$formMapper
->with('Networks')
->add('networks', 'sonata_type_model', array(
'by_reference' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'label' => 'Choose the user Networks',
'class' => 'AcmeTestBundle:Network'
))
->end();
}
}
Here's the problem:
When I edit an existing User via SonataAdmin and assign certain Networks to it changes are persisted to the database but the selector appears empty, as if no Networks were ever assigned. If I try to assign Networks again, I get a database constraint violation message (as it would be expected).
I discovered that for some reason the $networks ArrayCollection is returning NULL, as if the relationship could not be established from the User side:
$usr = $this->getUser();
$selected_networks = $usr->getNetworks(); // returns NULL
If I attempt to manage Users from the Network side, everything works fine (with no additions or changes to the actual code):
<?php
namespace Acme\TestBundle\Admin;
use Sonata\AdminBundle\Form\FormMapper;
class NetworkAdmin extends Admin
{
/**
* #param \Sonata\AdminBundle\Form\FormMapper $formMapper
*
* #return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('name')
->end()
->with('Users')
->add('users', 'sonata_type_model', array(
'by_reference' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'label' => 'Choose the network Users',
'class' => 'ApplicationSonataUserBundle:User'
))
;
}
}
I'm stumped. Any ideas?
Thank you all.