Add PersistentCollection to an object in fixture - symfony

I'm trying to create a new fixture for creating a user.
This is the fixture :
class UserFixtures extends Fixture implements DependentFixtureInterface
{
private ManagerRegistry $_managerRegistry;
public function __construct(ManagerRegistry $managerRegistry)
{
$this->_managerRegistry = $managerRegistry;
}
public function load(ObjectManager $manager)
{
$groups = $manager->getRepository(Group::class)->findAll(); // This return an array of object. I want a PersistentCollection
$company = $manager->getRepository(Company::class)->findOneBy(['company_name' => 'HANFF - Global Health Solution']);
$user = new User();
$user->setLogin("TEST_TEST")
->setName("TEST_Name")
->setFirstName("TEST_Firstname")
->setPassword("test")
->setEmail("TEST#hanff.lu");
$user->setCompany($company);
$user->setGroups($groups); // This don't work as it is just an array
$manager->persist($user);
$manager->flush();
}
/**
* #inheritDoc
*/
public function getDependencies()
{
return array(
CompanyFixture::class,
GroupFixture::class
);
}
}
So I have already created the company and group which persist into my database.
And now I want to set to my new user the company and group which have been previously persisted by doctrine.
This work for my company as this is a single object.
But this is not working for my groups as it is typed as a PersistenCollection object and the getRepository(Group::class)->findAll() return an array of object Group.
Here the data contains in the $groups variable :
array:2 [
0 => App\Entity\Group {#1039
-code: "NAT"
-label: "National"
}
1 => App\Entity\Group {#1044
-code: "VET"
-label: "Vétérinaire"
}
]
Here this is how I defined the groups into my User entity :
Class User{
// ...
/**
* #var PersistentCollection
* Many user has many groups
* #ORM\ManyToMany(targetEntity="Group")
* #ORM\JoinTable(name="user_group",
* joinColumns={#ORm\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_code", referencedColumnName="`code`")}
* )
*/
private PersistentCollection $groups;
public function getGroups(): PersistentCollection
{
return $this->groups;
}
public function setGroups(PersistentCollection $groups): self
{
$this->groups = $groups;
return $this;
}
public function addGroup($group): self
{
$this->getGroups()->add($group);
return $this;
}
// ...
}
I have read somewhere (can't remember where) that when you persist an object using doctrine, it can be accessed as a PersistentCollection but I can't figure how to do that (Except by creating a new PersistentCollection() which is certainly not the best manner to do it)?
I have tried setting ArrayCollection instead of PersistentCollection, but if I do that, doctrine yells at me when I try to persist my user object because it can't convert ArrayCollection to PersistentCollection (i guess).

You have to change the types of your properties, arguments and return values to the Doctrine\Common\Collections\Collection interface. That's the interface ArrayCollection and PersistentCollection share. Don't forget to initialize your groups property to an ArrayCollection in the constructor. Otherwise calls to addGroup will fail on new user entities.
class User
{
// ...
/**
* #ORM\ManyToMany(targetEntity="Group")
* #ORM\JoinTable(name="user_group",
* joinColumns={#ORm\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_code", referencedColumnName="`code`")}
* )
*/
private Collection $groups;
public function __construct()
{
// other initialization
$this->groups = new ArrayCollection();
}
public function getGroups(): Collection
{
return $this->groups;
}
public function setGroups(Collection $groups): self
{
$this->groups = $groups;
return $this;
}
public function addGroup($group): self
{
$this->getGroups()->add($group);
return $this;
}
// ...
}

Related

Easyadmin 3: field is not a Doctrine association, so it cannot be used as an association field

so this is the situation:
I have an entity ProductWhisky, which is a subclass of ProductAbstract, in which I refer to the Product entity (One-to-One). Product has many-to-one association to ProductProducer. I'm using Easyadmin 3 to edit this ProductWhisky.
So this is a part of my ProductWhiskyCrudController:
public function configureFields(string $pageName): iterable
{
// more fields
yield AssociationField::new('productProducer', 'Producent');
// even more fields
}
This is my ProductWhiskyEntity:
class ProductWhisky extends ProductAbstract
{
use ProductTrait;
//props and other methods specific to ProductWhisky
}
ProductTrait
trait ProductTrait
{
/**
* #ORM\OneToOne(targetEntity="App\Entity\Product", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $product;
public function getProduct(): ?Product
{
return $this->product;
}
public function setProduct(Product $product): self
{
$this->product = $product;
return $this;
}
/**
* #return ProductProducer|null
*/
public function getProductProducer(): ?ProductProducer
{
if ($this->getProduct()) {
return $this->getProduct()->getProducer();
}
return null;
}
Product entity:
class Product
{
// more props
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ProductProducer", inversedBy="products", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $producer;
// more props and methods
public function getProducer(): ?ProductProducer
{
return $this->producer;
}
public function setProducer(?ProductProducer $producer): self
{
$this->producer = $producer;
return $this;
}
So, when I call the ProductWhiskyCrudController, I always get the message The "productProducer" field is not a Doctrine association, so it cannot be used as an association field.
In EasyAdmin 2, I did this with this:
form:
fields:
- property: 'productProducer'
label: 'Producent'
type: entity
type_options: {class: 'App\Entity\ProductProducer', required: true}
Any idea how I could fix this?
All the best
Tim
You should change your
yield AssociationField::new('productProducer', 'Producent');
with
yield AssociationField::new('producer', 'Producent');
because your form has to refer to a Field, while you are referring it to an entity.

Reference setting value in entity symfony

I have a doubt about code organization using symfony3 and doctrine: I'll try to explain as clear as I can. Let's say I have a FootballClub entity:
class FootballClub
{
// other code
private $memberships;
public function addMembership(Membership $membership) : FootballClub
{
$this->memberships[] = $membership;
return $this;
}
public function removeMembership(Membership $membership) : bool
{
return $this->memberships->removeElement($membership);
}
}
The entity is in a many-to-one relationship with another entity, Membership, which represents the contract a player has with the club. Let's say each club
has only a limited number of membership it can acquire, number that is represented as a setting, for example, as a property in a Setting entity.
The question is: how should I reference that setting when removing a membership from the club and check that is respected? Entities should not have any dependency, so what would be the correct way to implement this? A service? can you provide an example? Thank you for your time.
You could create a Settings entity, linked in OneToOne relation with FootballCluc entity.
Define Settings like this and instanciate it in the FootballClub's constructor
Settings entity
/** #Entity */
class Settings
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $maxMembership;
// Other configurable properties ...
__constructor($maxMembership = 50)
{
$this->maxMembership = $maxMembership;
}
public function getMaxMembership()
{
return $this->maxMembership;
}
public function setMaxMembership($maxMembership)
{
$this->maxMembership = $maxMembership;
}
}
Football Entity
class FootballClub
{
/**
* One FootballClub has One Settings.
* #OneToOne(targetEntity="Settings")
* #JoinColumn(name="settings_id", referencedColumnName="id")
*/
private $settings;
// other code
private $memberships;
__constructor(Settings $settings = null)
{
if (null === $settings) {
$settings = new Settings();
}
$this->settings = $settings;
}
public function addMembership(Membership $membership) : FootballClub
{
if ($this->settings->getMaxMembership() <= count($this->memberships)) {
// throw new Exception("Max number of membership reached"); Strict mode
// return false // soft mode
}
$this->memberships-> = $membership;
return $this;
}
public function removeMembership(Membership $membership) : bool
{
return $this->memberships->removeElement($membership);
}
}

silex symfony doctrine ORM many to many: getRoles at login returns empty List

Hello Silex (and Symfony) experts,
I need to implement a database authentification User/Role model via Doctrine /ORM.
This is my silex composer setup:
"require": {
"silex/web-profiler": "^2.0",
"monolog/monolog": "1.13.*",
"symfony/twig-bridge": "^3.2",
"symfony/monolog-bridge": "^3.2",
"symfony/console": "^3.2",
"symfony/yaml": "^3.2",
"symfony/security-bundle": "^3.2",
"doctrine/orm": "^2.5",
"dflydev/doctrine-orm-service-provider": "^2.0",
"symfony/form": "^3.2",
"symfony/validator": "^3.2",
"symfony/config": "^3.2",
"symfony/doctrine-bridge": "^3.2",
"doctrine/migrations": "^1.5"
},
Users can register. Registered users can login and logout. Non registered visitors have anonymous role.
The symfony profiler is working, so I can see the security status (authentification/authoriszation). I also track the apache logfile for php errors.
I started from here https://github.com/fredjuvaux/silex-orm-user-provider (User from db, roles as array) and tried to expand it to get user roles from database via doctrine many-to-many relation.
There are:
class MyUserController (different user actions like user,edit, register,... )
class MyUserManager implements UserProviderInterface (loadUserByUsername, ...)
class MyUserServiceProvider implements ServiceProviderInterface, ControllerProviderInterface, BootableProviderInterface (controller routing and template setting)
The ORM entities are:
User:
/**
* MyUser
*
* #Entity
* #Table(name="myuser")
*/
class MyUser implements UserInterface, \Serializable
{
....
/**
* #ManyToMany(targetEntity="MyRole", inversedBy="users")
*
*/
private $roles;
...
* Constructor.
*
* #param string $email
*/
public function __construct($email)
{
$this->email = $email;
$this->created = time();
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
$this->roles = new ArrayCollection();
}
...
/**
*
* #return ArrayCollection list of the user's roles.
*/
public function getRoles()
{
$result = $this->roles->toArray(); // throws error for login:
// $result = $this->roles; // test // thhrows error : null object
dump($this->roles);
// $result = array("ROLE_USER", "ROLE_OTHER"); // static setting and
works for login
return $result;
}
...
}
Roles (implements Roleinterface)
/**
* MyRole
*
* #Entity
* #Table(name="myrole")
*/
class MyRole implements RoleInterface
{
/**
* #var string
* #Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* #ManyToMany(targetEntity="MyUser", mappedBy="roles")
*/
private $users;
...
/*
* methods for RoleInterface
* #return string|null A string representation of the role, or null
*/
public function getRole()
{
$result = $this->role;
return $result;
}
}
When a user registers, he gets for that session the ROLE_USER role,
authentification and authorisation are ok and a user is created in the
database.
Then I can assign new roles ("role_test1", "role_test2") in the controller for the new user, the many-to-many table myuser_myrole is filled (myuser_id myrole_id).
When I change the roles, they are correctly updated by the entity manager.
When I access the user Entity from the userController to work on it, I can access the assigned roles:
// MyUserController.php
$user = $em->getRepository('MyEntities\MyUser')->find($id);
$roles= $user->getRoles()
$role_length = count($roles);
$role_list = array();
for ($i=0; $i <$role_length ; $i++)
{
array_push($role_list,$roles[$i]->getRole()); // MyRole::getRole() prints out something to screen.
}
printf("<br> role-list:"); dump($role_list);
Calling this controller prints out the assigned roles via MyRole::getRole(), so ORM access works here.
Now comes the strange:
I want to login the new user with the login form.
When I use
// MyUser::getRoles()
return $this->roles;
It throws:
Argument 4 passed to Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken::__construct() must be of the type array, object given,
Ok, makes maybe sense because the $roles is an Doctrine ArrayCollection.
When I use
// MyUser::getRoles()
return $this->roles->toArray();
I can login with user password,but am not authenticated (yellow status). Dumping out the roles, I receive an empty array ArrayCollection.
roles:
ArrayCollection {#388 ▼
-elements: []
}
The UsernamePasswordToken has an empty role-array.
When I use
// MyUser::getRoles()
return array("ROLE_HELLO1", "ROLE_HELLO2"); // static role array with strings
I can login and am authenticated with these roles:
Roles
array:2 [▼
0 => "ROLE_HELLO1"
1 => "ROLE_HELLO2"
]
There are old docs about this (Managing Roles in the Database) for symfony 2 http://symfony.com/doc/2.0/cookbook/security/entity_provider.html, but it doesnt work in symfony3.
Here they use
//class User
public function getRoles()
{
return $this->groups->toArray();
}
//class Group extends Role (not RoleInterface, old?)
public function getRole()
{
return $this->role;
}
The actual symfony docs for user management do not show how to use roles stored in database.
In summary:
Login and user/role do not work as expected:
MyUser::getRoles()
does not receive the Roles from database via doctrine ORM.
has to return a string array of roles for login.
delivers the correct role association in another controller.
Questions:
(1) Is this a Silex specific issue?
(2) How to use it correctly or where is a good link/doc for a workaround?
(3) Does the method LoadUserByUsername() interfere with all this?
(4) Do I need a class MyUserRepository extends EntityRepository {} to do the query and get the Role List?
(5) Do I need to use the Role Hierarchy service?
(6) Are there special naming conventions(tablename or class name) for "user" and "role"?
I found many posts asking the same/similar but they do not help here.
Thank you for help, I am really stuck on that!
dirk
Try this:
public function getRoles()
{
return $this->roles->map(function (MyRole $role) {
return $role->getRole();
})->toArray();
}
You should also check if the relationship is correctly saved in database.
If there is ManyToMany relationship between MyUser and MyRole, you have to ensure that relationship is saved in both entities.
//class MyUser
public function addRole(MyRole $role)
{
$this-roles->add($role);
$role->users->add($user);
}
I had a break on this, but now it seems to work. Thank you miikes for the addRole() suggestion!
finally I have: MyUser.php:
//Myuser.php
/**
* MyUser
*
* #Entity
* #Table(name="myuser")
*/
class MyUser implements UserInterface, \Serializable //, ObjectManagerAware
{
...
/**
* #ManyToMany(targetEntity="MyRole", inversedBy="users")
*/
private $roles;
public function __construct($email)
{
(...)
$this->roles = new ArrayCollection();
/**
*
* #return ArrayCollection list of the user's roles.
*/
public function getRoles()
{
$result = $this->roles->toArray();
return $result;
}
public function assignToRole($role)
{
$this->roles[] = $role;
}
public function setRole($myrole)
{
$this->roles= $myrole;
}
public function hasRole($role)
{
return in_array(strtoupper($role), $this->getRoles(), true);
}
public function addRole(MyRole $role)
{
$this->roles->add($role);
//$role->users->addRole($this); // could not access roles->user->...
// because private variable in MyRole but it works
}
/**
* Remove the given role from the user.
*
* #param string $role
*/
public function removeRole($role)
{
dump($role);
$this->roles->removeElement($role);
}
(...) // other setters getters
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
$this->salt,
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
$this->salt,
) = unserialize($serialized);
}
}
and MyRole.php:
// MyRole.php
/**
* MyRole
*
* #Entity
* #Table(name="myrole")
*/
class MyRole implements RoleInterface
{
(...)
/**
* #ManyToMany(targetEntity="MyUser", mappedBy="roles")
*/
private $users;
/**
* #var string
* #Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/*
* methods for RoleInterface
* #return string|null A string representation of the role, or null
*/
public function getRole()
{
$result = $this->role;
return ($result);
}
public function setRole($role)
{
$this->role= $role;
return $this;
}
(...)
/**
* Constructor
*/
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* Add user
* #param \MyEntities\MyUser $user
* #return MyRole
*/
public function addUser($user)
{
$this->users[] = $user;
return $this;
}
public function setUser($user)
{
$this->users[] = $user;
return $this;
}
/**
* Remove user
*
* #param \MyEntities\MyUser $user
*/
public function removeUser($user)
{
$this->users->removeElement($user);
}
/**
* Get users
*
* #return ArrayCollection $users
*/
public function getUsers()
{
return $this->users;
}
/**
* __toString()
*
* #return string
*/
public function __toString()
{
return $this->bezeichnung;
}
}
With the help of the doctrine orm commands
vendor/bin/doctrine orm:validate-schema
vendor/bin/doctrine orm:schema-tool:update --dump-sql
the correct manyToMany table myuser_myrole was generated and the role setting works at login of a user.
I think, the most important was the correct use of the function addRole() (with this->roles->add($role), and not something like this->roles->addRole($role) ) to let doctrine do the magic stuff in the background.
Thanks for any help and comments!
dirk

Maintaining OneToMany links with a form

Imagine these 2 entities
Intervention
- items # OneToMany (no cascade)
addItem()
removeItem()
Item
- intervention # ManyToOne
When I'm doing an Intervention I want to select the Items concerned.
I use an Intervention form in which I can attach/unattach items
->add('items', EntityIdType::class, array(
'class' => Item::class,
'multiple' => true,
))
When the form is submitted, I see Doctrine calls my Intervention's addItem(), removeItem()
But when I empty any previously attached items (thus sending null as items), Doctrine tells me:
Neither the property "items" nor one of the methods "addItem()"/"removeItem()", "setItems()", "items()", "__set()" or "__call()" exist and have public access in class "AppBundle\Entity\Intervention".
The first question is: Why Doctrine is not finding my accessors when I send a null item list ?
My workaround for now is to implement a setItems() doing the adds/removes:
/**
* Set items
*
* #param $items
*
* #return Intervention
*/
public function setItems($items)
{
foreach ($this->items as $item) {
$this->removeItem($item);
}
if ($items) {
foreach ($items as $item) {
$this->addItem($item);
}
}
return $this;
}
I think you need to use ArrayCollection in your other side of the ManyToOne relationship.
Your AppBundle\Entity\Item entity class needs to have:
use AppBundle\Entity\Intervention;
//...
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Intervention", inverseBy="items")
*/
private $intervention;
/**
* #param Intervention $intervention
*/
public function setIntervention(Intervention $intervention){
$this->intervention = $intervention;
}
/**
* #return Intervention
*/
public function getIntervention(){
return $this->intervention;
}
Then in the AppBundle\Entity\Intervention entity class:
use Doctrine\Common\Collections\ArrayCollection;
//...
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Item", mappedBy="intervention")
*/
private $items;
public function __construct(){
$this->items = new ArrayCollection();
}
public function getItems(){
return $this->items;
}
public function setItems($items){
$this->items = $items;
}

Symfony2 form and Doctrine2 - creat foreign key in assigned entities fails

i'm trying to add my data into my database , i was trying to not use a formbuilder, inside that i put all my form into the controller,and my entity contains a foreign key but i got an this error :
Neither the property "id_classe" nor one of the methods "getIdClasse()", "idClasse()", "isIdClasse()", "hasIdClasse()", "__get()" exist and have public access in class "MyApp\SchoolBundle\Entity\Etudiant".
here is my function in the controller :
public function AjoutAction(Request $request)
{ $classe=new Etudiant();
$formBuilder = $this->get('form.factory')->createBuilder('form', $classe);
$formBuilder
->add('prenom', 'text')
->add('nom', 'text')
->add('Cin', 'integer')
->add('id_classe', 'integer')
->add('save', 'submit')
;
$form = $formBuilder->getForm();
if ($form->handleRequest($request)->isValid()) {
$objToPersist = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($objToPersist);
$em->flush();
}
return $this->render('MyAppSchoolBundle:Etudiant:ajout.html.twig',array(
'form' => $form->createView(),
));
}
and here is my Entity
namespace MyApp\SchoolBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
*/
class Etudiant {
/**
* #ORM\id
*#ORM\GeneratedValue
*#ORM\Column(type="integer",name="ID_Etudiant")
*/
private $Id;
/**
*#ORM\Column{type="string",length=255}
*/
private $prenom;
/**
*#ORM\Column{type="string",length=255}
*/
private $nom;
/**
*#Assert\NotBlank
*#ORM\Column(type="integer", unique=true)
*/
private $cin; //unique ne fonctionne pas qu'avec les assert
/**
* #ORM\ManyToOne(targetEntity="Classes",cascade={"ALL"})
*/
private $id_classe;
function getId() {
return $this->Id;
}
function getPrenom() {
return $this->prenom;
}
function getNom() {
return $this->nom;
}
function setId($Id) {
$this->Id = $Id;
}
function setPrenom($prenom) {
$this->prenom = $prenom;
}
function setNom($nom) {
$this->nom = $nom;
}
function getCin() {
return $this->cin;
}
function setCin($cin) {
$this->cin = $cin;
}
public function getId_classe() {
return $this->id_classe;
}
function setId_classe($id_classe) {
$this->id_classe = $id_classe;
}
}
In your form you have:
->add('id_classe', 'integer')
Add a setter in your entity
public function setIdClasse($idClasse) {
$this->id_classe = $idClasse;
}
Edit
Also, as a suggestion:
1 Always add visibility to your functions (public function blabla() or private function blabla())
2 Use camel case is preferred (so your properties are $nomClasse, $idClasse, $id, etc..)
3 Not compulsory, but a good idea to return the object in your setter
4 You're not very consistent in your notations (see your form builder->add('nom', 'text') ->add('Cin', 'integer'))
Getters and Setter would normally look like this:
public function getNomClasse()
{
return $this->nomClasse;
}
public function setNomClasse($nomClasse)
{
$this->nomClasse = $nomClasse;
return $this;
}

Resources