I tried creating a multi column unique validation constraint but it won't work. Here is my model:
/**
* User
*
* #ORM\Entity
* #UniqueEntity({"webinar", "email"})
*/
class User implements UserInterface, \Serializable {
...
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255)
* #Assert\NotBlank()
* #Assert\Email()
* #Assert\Length(max="255")
*/
private $email;
...
/**
* #ORM\ManyToOne(targetEntity="Wefra\ADHSWebinarBundle\Entity\Webinar", inversedBy="registeredUsers")
* #ORM\JoinColumn(name="webinar_id", referencedColumnName="id")
*/
private $webinar;
...
}
What is happening is that, even though the two columns match the validation throws no error.
E.g. user1 hast the email example#example.com and webinar_id 6 and user2 tries to register with the same data without the validation generating an error.
I'm using Symfony2.5
Whoops, i found the problem. The webinar was not set when validating the model. I had to set the webinar_id before validating the Model.
Related
I would like to include related questions and question groups as list of IRIs when I make get request to Form.
I have few entities:
Form (fields: question_groups, questions)
QuestionGroup (fields: form, questions)
Question (fields: form, question_group)
When I make get request for the form, it returns:
Form
questions ['/api/questions/1',...]
questionGroups [{id:1,name:'foo',questions: ['/api/questions/1',...]...},...]
As you can see, Form.questions is list of IRIs but Form.questionGroups is list of objects. I would like to have both of them as IRIs.
On the image below under questionGroups field there is a questionGroup field but there is no question field under questions or answer under answers,...
The whole thing makes no sense to me, I have tried to set #MaxDepth, it changed nothing (except when i have used #MaxDepth(0) which threw 500 error without any error massage in response or in php log)
Can anyone explain why, and what should I do to load both questions and questionGroups as list of IRIs?
Thank you
Here are relevant parts of entities mentioned above.
/**
* #ORM\Entity
* #ApiResource
* #ORM\HasLifecycleCallbacks
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class Form
{
/**
* #ORM\Column(type="text", length=200, nullable=false)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="QuestionGroup", mappedBy="form", cascade={"REMOVE"})
*/
private $question_groups;
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="form", cascade={"REMOVE"})
*/
private $questions;
}
/**
* #ORM\Entity
* #ApiResource
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class QuestionGroup
{
/**
* #ORM\Column(type="string", length=200, nullable=false)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Form",inversedBy="question_groups")
*/
private $form;
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="question_group", cascade={"REMOVE"})
*/
private $questions;
}
/**
* #ORM\Entity
* #ApiResource
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class Question
{
/**
* #ORM\Column(type="string", length=200, nullable=false)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Form",inversedBy="questions")
*/
private $form;
/**
* #ORM\ManyToOne(targetEntity="QuestionGroup", inversedBy="questions")
*/
private $question_group;
}
I have figured out that if Entity field/property is snake_case like "$question_groups" then API will return it as list of objects and if its camelCase like "$questionGroups" it will be returned as list of IRIs. Side note: normalisationContext does not like snake_case. if you use normalisationContext and the property is snake_cased than it will be not included in the response data.
In my user entity, I have two fields (username and email)
and I want them to be mutually unique. I put the Annotation « UniqueEntity » on the top of my entity class and a unique property on each field like this :
/**
* Class User
* #package App\Entity
* #ORM\Table(name="user", uniqueConstraints={
* #ORM\UniqueConstraint(name="app_user_email", columns="email"),
* #ORM\UniqueConstraint(name="app_user_username", columns="username")
* })
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"})
* #UniqueEntity(fields={"username"})
*/
class User implements UserInterface, \Serializable
{
....
/**
* #var string
*
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank
*/
private $username;
/**
* #var string
*
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\Email
* #Assert\NotBlank
* #Assert\Length(max=255, maxMessage="...")
*/
private $email;
....
}
In my database I can't have 2 identical email or username. This is a valid point. I want moreover that a value in one of the two fields is not found in one of the other two fields.
For example, if an entry in the User table has a specific email value, I do not want to be able to enter the same value in the username field for an other user.
What is the best way to do this ?
The purpose of all this is for a user to be able to authenticate with his username or email and if a mail for a user is equal to a username of another user it will cause a problem.
Hy, in your entity you can add a callback constraint
First add
use Symfony\Component\Validator\Context\ExecutionContextInterface;
and in your class add
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context, $payload)
{
if ($this->username == $this->email) {
$context->buildViolation('The username can\'t be the email')
->atPath('username')
->addViolation();
}
}
check https://symfony.com/doc/current/reference/constraints/Callback.html for your symfony version
I have the following code in Symfony 3:
A class Appointment
<?php
/**
* Appointment
*
* #ORM\Entity
* #ORM\Table(name="ev_appointment")
*/
class Appointment
{
/**
* #ORM\OneToMany(targetEntity="EmailForward", mappedBy="_appointment")
*/
private $_email_forwards;
/**
* #ORM\OneToMany(targetEntity="ParticipationRequest", mappedBy="_appointment")
*/
private $_participation_requests;
}
A class EmailForward
<?php
/**
* #ORM\Entity
* #ORM\Table(name="ev_email_forward")
*/
class EmailForward
{
/**
* #ORM\ManyToOne(targetEntity="Appointment" , inversedBy="_email_forwards")
* #ORM\JoinColumn(name="ev_appointment_id", referencedColumnName="id")
*/
private $_appointment;
/**
* #ORM\Column(type="string", length=255, name="email", nullable=true)
*/
private $_email;
/**
* #ORM\Column(type="datetime", name="forwarded_at", nullable=true)
*/
private $_forwarded_at;
/**
* #ORM\Column(type="string", length=255, name="source", nullable=true)
*/
private $_source;
}
A class ParticipationRequest
<?php
/**
* #ORM\Entity
* #ORM\Table(name="ev_participation_request")
*/
class ParticipationRequest
{
/**
* #ORM\ManyToOne(targetEntity="Appointment", inversedBy="_participation_requests")
* #ORM\JoinColumn(name="ev_appointment_id", referencedColumnName="id")
*/
private $_appointment;
/**
* #ORM\Column(type="string", length=255, name="email", nullable=true)
*/
private $_email;
/**
* #ORM\Column(type="datetime", name="forwarded_at", nullable=true)
*/
private $_forwarded_at;
/**
* #ORM\Column(type="string", length=255, name="source", nullable=true)
*/
private $_source;
}
Now seems to me like I have 2 relationships with 2 Entities that have the exact same structure. So I am wondering, what is the right way to go?
On the one hand I could leave it as it is, because it does, work. But again, if some information was to be the same in both fields, it seems kinda like a waste to have 2 DB entries with the exact same info, and also harder to mantain afterwards.
Is there a more intelligent approach to solve this issue?
You could use e.g. Single Table Inheritance.
By that you only define the structure for EmailForward and ParticipationRequest once and all data will be persisted in one table in the database. During ORM mapping Doctrine will recognize which type you're using and instantiate the correct Object for you.
I don't see how to solve the 'if data is same in both relations it will be peristed twice' because
if it was always the same you wouldn't need two relations
there is no real way to keep it in one persistence - only option I see would be to create another relation from EmailForward and ParticipationRequest which keeps the data which might be needed twice and is referenced from both Objects then.
I'm using JMSSerializerBundle and FOSRestBundle and I'm trying to deserialize my body request by means of the #ParamConverter annotation:
/**
* #View()
*
* #Route("/users/{username}/globaltoken", defaults={"_format" = "json"}, requirements={"user"="\w+"})
* #ParamConverter(
* "userBody", class="Belka\AuthBundle\Entity\User",
* converter="fos_rest.request_body"
* )
*/
public function postAction($username, User $userBody)
{
...
The User entity has #ExclusionPolicy("all") set and some attributes are #exposed. That's perfect when I serialize; unfortunatly, when it comes to deserializing my body into a User object the unexposed attribtues are not set. Is there a clean way to handle this?
Answering myself: #ExclusionPolicy(“all”) is not what you want for security purposes. That tag was born for handling data that should not be serialized, whether or not sometimes it should not appear for security reasons. It's a static thing and it's OK like that.
What I really wanted is managing what to show or not (or consider for deserialization) by using groups. Hence:
Declare some groups and assign on the attributes
Use the desired groups in the controller: the deserialization and serialization will consider only the attributes belonging to at least one group declared.
An example:
* Entity *
class User implements EncoderAwareInterface
{
/**
* #ORM\Id
* #ORM\Column(type="string")
* #Assert\NotBlank(message = "user.username.not_blank")
* #ORM\GeneratedValue(strategy="NONE")
* #Serializer\Groups({"default"})
*/
private $username;
/**
* #ORM\Column(type="string", nullable=true)
* #Serializer\Groups("personal")
*/
private $password;
...
* Controller *
/**
* #ParamConverter(
* "userBody",
* class="Belka\AuthBundle\Entity\User",
* converter="fos_rest.request_body",
* options={"deserializationContext"={"groups"={"personal"}}}
* )
*/
public function postAction($username, User $userBody, $_format)
{
that way, only password will be deserialized.
I started using symfony not long ago and at the moment I'm struggling with this problem:
I decided to have "who" information at entity level so I have defined these additional 4 prameters for every entity:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
private $createdBy;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
private $updatedAt;
My problem is now where and how I should populate createdBy and updatedBy. ATM I do that in my controller before persisting to the database. Thou I encountered a problem when a entity is a property of another entity and lets say I have an entity called Post that has a property images of type Document the entities Post and Document both have "who" information on them and images property inside Post is defined as follows:
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Nisand\DocumentsBundle\Entity\Document", cascade={"persist"})
* #ORM\JoinTable(name="blog_documents",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="document_id", referencedColumnName="id")}
* )
*/
private $images;
For Post suppose I set createdBy in the controller before persisting but on Document how should that work cause that will be persisted by the cascade rule?
How do you guys handle in your applications the "who" columns?
Try this bundle: StofDoctrineExtensionsBundle and use Blameable extension.
You will need set current user with BlameableListener. And it will cover your use case.
Documentation for Blameable is here: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md