I've got an entity with some validators (not a form).
So I use $validator->validate($entity), but it doesn't validate my sub-ojects (the entity class has some others entity classes with some validators).
Is there an "automatic" way to do this, or I have to do $errorList->addAll($validator->validate($entity)); for each of them ?
To allow recursive validation over objects you can simply use the Constraint #Assert\Valid
Example
Say a person has a mandatory last name
class Person
{
/**
* #Assert\NotNull
* #var string
*/
protected $lastName;
}
And you have a product, which have a buyer (Person)
class Product
{
/**
* #Assert\NotNull
* #Assert\Valid
* #var Person
*/
protected $buyer;
}
By having NotNull and Valid, each time you validate the Product model it will check that:
It has a buyer
The buyer has a lastName
Related
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.
How can I cascade a joined model with a OneToOne relationship where by only the User table has an auto increment strategy and the joined Profile model must have an id that matches the User id.
My models look like this:
Company\Model\User:
class User
{
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
* #var int
*/
private $id;
/**
* #OneToOne(targetEntity="Profile", inversedBy="user", cascade={"persist"})
* #var Profile
*/
private $profile;
Company\Model\Profile:
class Profile
{
/**
* #Id
* #OneToOne(targetEntity="User", mappedBy="profile")
* #JoinColumn(name="id")
* #var User
*/
private $user;
When persisting an instance of the User model, it causes the following error to be output:
Entity of type Company\Model\Profile is missing an assigned ID for field 'profile'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called. If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.
The doctrine documentation calls this a simple derived identity, but does not explain how to cascade it.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/composite-primary-keys.html#use-case-2-simple-derived-identity
It turns out that the answer is actually quite simple.
First the mappedBy and inversedBy need to be swapped around.
Secondly, when setting the Profile on the User you must set the user on the profile in turn.
Company\Model\User:
class User
{
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
* #var int
*/
private $id;
/**
* #OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist"})
* #var Profile
*/
private $profile;
public function setProfile(Profile $profile): void
{
$this->profile = $profile;
$this->profile->setUser($this);
}
Company\Model\Profile:
class Profile
{
/**
* #Id
* #OneToOne(targetEntity="User", inversedBy="profile")
* #JoinColumn(name="id")
* #var User
*/
private $user;
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.
I have a User Entity. This is considered the primary entity in this case and the mere fact it is being used means it is present.
The User entity, has a Store entity. But not all Users will necessarily have a Store entity.
It is worth noting that this is an existing database we are working with, and the id for the User table is the same as the id for the Store table. Name (id) and Value. It's just that in some cases, Store does not have a record for a given User id.
User:
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $store;
...
}
Store:
class Store extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
...
}
This causes problems in the controllers. If a User entity does not have a Store record, it fails with a "Entity not found" exception. This can be dealt with using a try catch easy enough (I haven't been able to find a way to check if an Entity object exists or is just a proxy). If the User does have a store record, all is fine here.
But the big issue I have is especially the Fixtures:
protected function createUser($id)
{
$user = new User();
$user->setId($id);
$user->setEmail($id.'#example.com');
$user->setUserName($id.'_name');
$user->setArea($this->manager->find('Area', 156)); // Global
$this->manager->persist($user);
return $user;
}
When I run Fixtures, this fails. Giving me the error "Integrity constraint violation: 1048 Column 'id' cannot be null". This message disappears if I remove the store entity from User. So in a nutshell, I cannot add a user if it doesn't have a store.
Anyone know what's happening? I've done some looking around and I can't find anything, including doctrine docs, on having optional relationships between Entities. Which I thought would have been a common situation.
Found the solution to this on this doc page:
http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata
In my case, rather than the User entity being associated with the Store entity using the id field, the store property in the User entity would be associated to the Store entity by user (an entity object). In return, the Store object will hold a User entity, which is annotated as the entity's id.
I'm sure that's as confusing as hell, so just look at the sample above. Below are my adjusted Entity classes:
User
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store", mappedBy="user")
*/
protected $store;
...
}
Store
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}
Now, if there is no Store record present for a given User, the store property in the User entity will be null. Fixtures runs as expected too.
In addition to the answer above, I also needed to add an inversedBy attribute. Otherwise, an invalid Entity mapping error will be thrown.
Using the entities above, the Store object would need to look like this:
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}
I wanted to have a created_by field for my model, say Product, that is automatically updated and I am using FOSUserBundle and Doctrine2. What is the recommended way of inputting the User id into Product?
Can I do it in the Product model? I am not sure how to do so and any help would be wonderful. Thanks!
I want to do something like this in the model, but I don't know how to get the user id.
/**
* Set updatedBy
*
* #ORM\PrePersist
* #ORM\PreUpdate
* #param integer $updatedBy
*/
public function setUpdatedBy($updatedBy=null)
{
if (is_null($updatedBy)) {
$updatedBy = $user->id;
}
$this->updatedBy = $updatedBy;
}
To relate the user to the product you want to associate the two entities:
http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="products")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* You may need to use the full namespace above instead of just User if the
* User entity is not in the same bundle e.g FOS\UserBundle\Entity\User
* the example is just a guess of the top of my head for the fos namespace though
*/
protected $user;
and for the automatic update field you may be after lifecyclecallbacks:
http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* #ORM\PreUpdate
*/
public function setCreatedValue()
{
$this->created = new \DateTime();
}
}
EDIT
This discussion talks about getting the container in the entity in which case you could then get the security.context and find the user id from that if you mean to associate the current user to the product they edited:
https://groups.google.com/forum/?fromgroups#!topic/symfony2/6scSB0Kgds0
//once you have the container you can get the session
$user= $this->container->get('security.context')->getToken()->getUser();
$updated_at = $user->getId();
Maybe that is what you are after, not sure it is a good idea to have the container in the entity though, could you not just set the user on the product in the update action in your product controller:
public function updateAction(){
//....
$user= $this->get('security.context')->getToken()->getUser();
$product->setUser($user)
}