EasyAdminBundle many-to-many unidrectional entities relationship - symfony

After gone through many google links, I want to know what is the very common way to use "EasyAdminBundle" for doctrine entities mapping.
I am novice in symfony. I have already updated my doctrine schema as given below.
/* User Entity */
class User{
/**
* #ORM\ManyToMany(targetEntity="UserGroup")
* #ORM\JoinTable(name="users_groups",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $UserGroup;
}
/**
* #return string
*/
public function __toString(){
return $this->getUserGroup();
}
/* UserGroup Entity */
Class UserGroup{
//...
}
It has created a new table to refer two foreign keys User(id) UserGroup(id)
Now I try to open an user record in my EasyAdminBundle(BackOffice), It shoot an error when open a record.
Catchable Fatal Error: Object of class UserBundle\Entity\UserGroup
could not be converted to string
As i have seen, EasyAdminBundle supports many-to-many entity relations. But I don't find any information about it.
Is there any way to do itself in config.yml?

You need to define a __toString() method in your UserGroup entity class which should return a string.

Related

How can I have two User that share the same table?

I have an entity User with lots of feature built for it.
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
}
This entity is good as is for most of my User.
However, a few of them will have a special ROLE, ROLE_TEACHER.
With this role, I need to store a lot of new variables specially for them.
If I create a new entity Teacher, doctrine creates a new table with every User's data + the Teacher's data.
/**
* #ORM\Entity(repositoryClass="App\Repository\TeacherRepository")
* #Table(name="teachers")
*/
class Teacher extends User
{
/**
* #ORM\Column(type="string", length=64, nullable=true)
*/
protected $test;
public function __construct()
{
parent::__construct();
}
}
What I want, is for Teacher & User to share the users table and have the teachers table only store the extra data. How could I achieve that ?
This is more of system design problem than implementation problem. as #Gary suggested you can make use of Inheritance Mapping which can have Performance issues, I'd rather suggest re think your schema and make use of database normalization techniques to break up your data into more manageable entities.
You can have User entity :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
/**
* One user has many attibute data. This is the inverse side.
* #OneToMany(targetEntity="UserData", mappedBy="data")
*/
private $data;
}
With other UserData Entity with OneToMany relationship :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserDataRepository")
* #Table(name="user_data")
*/
class UserData
{
/* variables + getter & setter */
#ORM\Id()
private $id;
/**
* Many features have one product. This is the owning side.
* #ManyToOne(targetEntity="User", inversedBy="data")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\Column(type="string")
*/
private $attribute;
/*
* #ORM\Column(name="value", type="object")
*/
private $value;
}
Now you can have list of user attributes without requiring specific structure to each role. It's scalable and arbitrary.
You can also define same Relation with TeacherData, StudentData or UserProfile Entities with foreign keys and branch your application logic according to the roles. Key is to break data into their separate domains and keep common data in one table. Load related data by querying related entity, this increases readability and makes it easy to break complex structure into manageable codebase.

Symfony: How to configure embedded form to create new child object?

I'm building a user management page where I create or edit users.
The user consists of two entities, user and profile, which have a one to one relationship (I would merge, but can't for historical reasons).
/* User.php - Entity Class
/**
* #var Profile
* #Assert\Type(type="App\Entity\Profile")
* #Assert\Valid()
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist"})
*/
private $profile;
/* Profile.php - Profile Entity Class
/**
* #var \App\Entity\User
*
* #ORM\OneToOne(targetEntity="App\Entity\User", inversedBy="profile")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
I built two forms.
One form is the profile form which contains all the essential profile fields (first_name, last_name, email), although does not explicitly contain the relation field (user_id).
The other form is the user form, which contains the basic user fields (username, password), and also includes the profile form.
$builder->add('profile', ProfileForm::class);
When I use this form for editting, everything works fine, and changes to both objects persist. But when I try to use the form to create a new user, it fails, saying that I'm missing user_id.
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
It seems like this should work, but I'm missing something.
Thanks to #Cerad for this answer. I was not setting the link in my User entity setProfile class.
/**
* Set profile
*
* #param \App\Entity\Profile $profile
*
* #return SfGuardUser
*/
public function setProfile(\App\Entity\Profile $profile = null)
{
$this->profile = $profile;
$this->profile->setUser($this); // Adding this line fixed my issue
return $this;
}

Symfony entity no configured for cascade

Can anyone help me explaining what I am doing wrong? I try to set an entity in relation to an file. Its about Supplier and Stock.
My Stock entity looks like
/**
* #ORM\ManyToOne(targetEntity="PrClientBundle\Entity\Lieferanten")
* #ORM\JoinColumn(name="liefer_id", referencedColumnName="id")
* #var lieferant
*/
private $lieferant;
I also using getter and setter like following
/**
* Set lieferant
*
* #param \PrClientBundle\Entity\Lieferanten $lieferant
* #return Leadbase
*/
public function setLieferant(\PrClientBundle\Entity\Lieferanten $lieferant = null)
{
$this->lieferant = $lieferant;
return $this;
}
/**
* Get lieferant
*
* #return \PrClientBundle\Entity\Lieferanten
*/
public function getLieferant()
{
return $this->lieferant;
}
When I import new Stockitems like:
$lead->setLieferant($lieferant);
I get the following errormessage which I really don't understand :(
[Doctrine\ORM\ORMInvalidArgumentException]
A new entity was found through the relationship 'PrLeadBundle\Entity\Leadbase#lieferant' that was not configured to cascade persist operations for entity: PrClientBundle\Enti
ty\Lieferanten#000000002a45dae80000000002f826ff. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this
association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'PrClientBundle\Entity\Lieferante
n#__toString()' to get a clue.
It would be very great if you could help me understanding what am I doing wrong.

Make another entity from entity by Doctrine

I have two tables, user and userAttr.
'user' and 'userAttr' are tied as onebyone.
I would like to insert a row in userAttr when row is inserted user.
So this is my idea.
Make new data row of userAttr In prePersist() method in user entity.
in Acme/UserBundle/Entity/User.php
class User extends BaseUser implements ParticipantInterface
{
public function prePersist()
{
$userAttr = new userAttr();
$userAttr->setUser($this);
$userAttr->setEnabled(true);
$this->setUserAttr($userAttr);
$em = $this->getDoctrine()->getManager();
$em->persist($userAttr);
$em->flush();
but it shows error like this.
Fatal error: Call to undefined method Acme\UserBundle\Entity\User::getDoctrine() in
There are two quesions.
1.Is my basic idea correct?
2.How can I get the instance of doctrine in entity class?
Generally you got a little fallacy in there. Once the entity manager persists a entry it will also persist the related one-to-one connection. So if you persist $userAttr - which is one-to-one connected to your instance of User - it will persist User before it should get persisted. Causing double writings in the database. You can avoid this by adjusting your prePersist() to
public function prePersist()
{
$userAttr = new userAttr();
$userAttr->setUser($this);
$userAttr->setEnabled(true);
$this->setUserAttr($userAttr);
}
This avoids finding a way to get a instance of the entity manager too.
I'll answer your second question first.
How can I get the instance of doctrine in entity class?
You can't and you shouldn't. Your entity class is just a model, it has no knowledge of Doctrine, Symfony or the Entity Manager. Persistence will be handled at a higher level.
Is my basic idea correct?
No. As I said in the previous point, persistence shouldn't be a worry at this level. Here, you're just defining the properties and relations of your model.
I imagine your entity looks somewhat like this:
class User extends BaseUser implements ParticipantInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #ORM\OneToOne(targetEntity="UserAttr", cascade={"persist"})
*/
private $userAttr;
public function setUserAttr(UserAttr $userAttr = null)
{
$this->userAttr = $userAttr;
return $this;
}
public function getUserAttr()
{
return $this->technician;
}
}
Note the cascade={"persist"} option in the relation with UserAttr. This is what tells Doctrine that it should insert that into the database, too.
Further reading
Doctrine: One-To-One, Unidirectional
Doctrine: Transitive persistence / Cascade Operations
Symfony: Persisting Objects to the Database

Symfony2 Doctrine2 trouble with optional one to one relation

I have a problem with Doctrine2 in Symfony2 and two relationed entities.
There is a user-entity that can (not must) have a usermeta-entity referenced which contains information like biography etc.
The usermeta is optional because user is imported by another system, while usermeta is managed in my application.
Of course I want to save both together, so that saving a user must create or update a usermeta-entity.
Both are joined by a column named aduserid (same name in both tables).
I've recognized that if usermeta is an optional reference the owning-side in this case should be usermeta, otherwise doctrine loads user and needs the usermeta entity - but it's not always there.
Please note the comments in User->setMeta..
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User
{
/**
* #var Usermeta
* #ORM\OneToOne(targetEntity="Usermeta", mappedBy="user", cascade={"persist"})
*/
protected $meta;
public function getMeta()
{
return $this->meta;
}
/**
*
* #param Usermeta $metaValue
*/
public function setMeta($metaValue)
{
// I've tried setting the join-column-value here
// - but it's not getting persisted
// $metaValue->setAduserid($this->getAduserid());
// Then I've tried to set the user-object in Usermeta - but then
// it seems like Doctrine wants to update Usermeta and searches
// for ValId names aduserid (in BasicEntityPersister->_prepareUpdateData)
// but only id is given - so not luck here
// $metaValue->setUser($this);
$this->meta = $metaValue;
}
/**
* #var integer
*
* #ORM\Column(name="rowid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Get rowid
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
// some mor fields....
}
And the Usermeta class:
/**
* Usermeta
*
* #ORM\Table(name="userMeta")
* #ORM\Entity
*/
class Usermeta
{
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="meta")
* #ORM\JoinColumn(name="ADuserid", referencedColumnName="ADuserid")
*/
protected $user;
public function getUser()
{
return $this->$user;
}
public function setUser($userObj)
{
$this->user = $userObj;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
}
the controller code looks like this:
...
$userForm->bind($request);
if($userForm->isValid()) {
$em->persist($user);
$em->flush();
}
...
The Zdenek Machek comment is almost correct. As you can see from the Doctrine2 documentation, the nullable option should be in the join annotation (#JoinColumn), not in the mapping one (#OneToOne).
#JoinColumn doc:
This annotation is used in the context of relations in #ManyToOne, #OneToOne fields and in the Context of #JoinTable nested inside a #ManyToMany. This annotation is not required. If its not specified the attributes name and referencedColumnName are inferred from the table and primary key names.
Required attributes:
name: Column name that holds the foreign key identifier for this relation. In the context of #JoinTable it specifies the column name in the join table.
referencedColumnName: Name of the primary key identifier that is used for joining of this relation.
Optional attributes:
unique: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false.
nullable: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true.
onDelete: Cascade Action (Database-level)
onUpdate: Cascade Action (Database-level)
columnDefinition: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on #JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a “columnDefinition” attribute on #Column also sets the related #JoinColumn’s columnDefinition. This is necessary to make foreign keys work.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#annref-joincolumn
#OneToOne doc:
The #OneToOne annotation works almost exactly as the #ManyToOne with one additional option that can be specified. The configuration defaults for #JoinColumn using the target entity table and primary key column names apply here too.
Required attributes:
targetEntity: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. IMPORTANT: No leading backslash!
Optional attributes:
cascade: Cascade Option
fetch: One of LAZY or EAGER
orphanRemoval: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false.
inversedBy: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#onetoone
You're using the wrong type of Relation for your problem.
What you want is a unidirectional one to one from Usermeta to User.
A bidirectional one to one relationship would mean the following:
A user MUST have a Usermeta object.
A Usermeta object MUST have a User.
In your case you're only trying to require the second condition.
This does mean that you can only hydrate User from Usermeta and not the other way around.
Unfortunately doctrine does not support Zero or One to Many relationships.
I got the error message "spl_object_hash() expects parameter 1 to be object, null given in..." while trying the same thing. I tried to define a bidirectional One to One relationship while the inversed value could be null. This gave the error message. Taking away the inversed side of the relationship solved the problem.
It is a pity that Zero or One to One relationships aren't supported.
I hope I do not disturb anyone by submitting this very late answer, but here is how I solved this problem:
/**
* #var Takeabyte\GripBundle\Entity\PDF
* #ORM\OneToOne(targetEntity="Takeabyte\GripBundle\Entity\PDF", inversedBy="element", fetch="EAGER", orphanRemoval=true)
*/
protected $pdf = null;
I added = null; to the attribute declaration. I hope this is of any help for anyone who reads this.
Reading my own old question is quite fun since I see the problem at first glance now..
When it came to a solution I've thought that doctrine can only handle Ids named "id", but ... aduserid is just not marked as ID, it's missing the Id annotation and doctrine cannot use the fields for the join column..
Second thing, Zdenek Machek was right: It has to be marked as nullable.

Resources