Extract constraints form Doctrine 2 entity in symfony 2 - php

To keep the field level constraints at a central place (not replicate it in each form), I added the constraints in the entity. Like below (lets say its one of the fields of a user entity):
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, nullable=false)
*
* #Constraints\NotBlank(
* groups={"register", "edit"},
* message="email cannot be blank."
* )
* #Constraints\Email(
* groups={"register", "edit"},
* message="Please enter a valid email address."
* )
*
* #Expose
* #Groups({"list", "details"})
*/
private $email;
Now I need a way to expose this validation constraints for each field which is an annotation of "Symfony\Component\Validator\Constraints". Is there a way that I can get all the constraints for all fields in the entity, like:
$em->getValidationConstraints('MyBundle:EntityUser'); //em is the entity manager
//and it returns me all the fields with its name, type and any constraints
//attached to it as any array
Thanks in advance.

Gather Information
Before fixing a problem, it's good to know what you are talking about and gather some information.
Doctrine is an ORM, something that does nice things between a database and an object. It has nothing to do with validation, that is done by the Symfony2 Validator Component. So you need something else than the $em.
All constraints of a class are called 'metadata' and they are usually stored in Symfony\Component\Validator\Mapping\ClassMetadata. We have to find a class which accepts the name of a class and returns a ClassMetadata instance.
To load the constraints, the Symfony2 Validator component uses loaders.
The Solution
We can see that there is a Symfony\Component\Validator\Mapping\ClassMetadataFactory. A factory is always used to build a class from a specific argument. In this case, we know it will create a ClassMetadata and we can see that it accepts a classname. We have to call ClassMetadataFactory::getMetadataFor.
But we see it needs some loaders. We aren't going to do the big job of initializing this factory, what about using the service container? We can see that the container has a validator.mapping.class_metadata_factory service, which is exactly the class we need.
Now we have all of that, let's use it:
// ... in a controller (maybe a seperated class is beter...)
public function someAction()
{
$metadataFactory = $this->get('validator.mapping.class_metadata_factory');
$metadata = $metadataFactory->getMetadataFor('Acme\DemoBundle\Entity\EntityUser');
}
Now we have the metadata, we only need to convert that to an array:
// ...
$propertiesMetadata = $metadata->properties;
$constraints = array();
foreach ($propertiesMetadata as $propertyMetadata) {
$constraints[$propertyMetadata->name] = $property->constraints;
}
Now, $constraints is an array with all fields and their constraint data, something like:
Array (
...
[email] => Array (
[0] => <instance of NotBlank>
[1] => <instance of Email>
),
)

Related

Symfony3: Binding Form to Entity Classes

I'm creating a form using built-in Symfony3 services:
1) creating new class in AppBundle\Form which extends AbstractType
2) creating a form object in my controller (using createForm())
3) pushing that object directly to twig layer (by createView())
In my Entities direction I've got two classes, already mapped to database by ORM.
First one is User, and the second one is UserAttribute. User is related to UserAttribute by OneToMany annotation. Relationship looks like:
class UserAttr
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userAttr" )
* #ORM\JoinColumn(nullable=false)
*/
private $user;
And from the User side:
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="UserAttr", mappedBy="user")
* #ORM\JoinColumn(nullable=false)
*/
private $userAttr;
When I'm adding new fields (using $builder->add()) everything works fine if they are associated to User class properties. But if I'm doing the same with UserAttribute properties - symfony can't find get/set methods for that properties. I know - I can fix it by class User extends UserAttribute but probably that's not the point. Symfony must have another solution for that, probably I missed something.
Thanks for your time !
// SOLVED | there should be defined an EntityClassType as below:
$builder->add('credit',EntityType::class,array(
'class' => UserAttr::class
));
You have One-To-Many Association to UserAttr from User Entity. Hence, A User might have multiple credit.
OPTION 1 :
Considering this, you have to use a collection field type in UserFormType, which is a bit lengthy process.
$builder->add('userAttr', CollectionType::class, array(
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'entry_type' => UserAttrType::class
));
Then create another FormType : UserAttrType to represent UserAttr where you can have the credit field as a property from UserAttr.
$builder
->add('credit', TextType::class, array(
'label' => 'Credit',
))
This way, The form will load and submit accordingly, The credit value will also be updated when User form gets updated. Here is a link for collection docs. This is how to embed a collection form.
OPTION 2 :
But, If you want to simplify it further, add mapped = false (doc) to the credit field. This will ignore the current error. However, you have to manually collect the credit value from Form Object and set value to appropriate UserAttr Object in Submit Handler.
Hope it helps!

Symfony2 Validation using Doctrine2 Annotations

I have the following Doctrine entity and I want to use its restriction also for validation.
/**
* #var string
*
* #ORM\Column(type="string", length=40)
* #Assert\Valid
*/
private $birthName;
I use the following validation, which works for symfony specific annotaions but not Doctrine set restrictions!
// Validate data
$validator = $this->get('validator');
$errors = $validator->validate($user);
if (count($errors) > 0) {
$response = new JsonResponse(array(
'error' => 'User could not be created.' . PHP_EOL . (string)$errors
));
$response->setStatusCode(400);
return $response;
}
What can I do to let symfony validator use the doctrine restrictions as settings?
Status quo:
I read [1] and [2] but so far I do not use forms because I have a controller returning JSON. If you know how to make this work with forms would also help a lot!
Doctrine mappings have nothing to do with validation.
The code #ORM\Column(type="string", length=40) only maps a property to a database field, and sets max length of a database field to be equal 40 characters, if you would create a schema using doctrine.
But thus doesn't take any part of the validation process.
So you need to set an assertion rule by something like
/**
* #Assert\Length(max = 40)
*/

How to handle Primary/Secondary/Normal statuses for entity in Symfony2

I am developing an application and I came across the following: Lets say I have an entity called Contact, that Contact belongs to a Company and the Company has a Primary Contact and a Secondary Contact and also has the remaining Contacts which I've named Normal.
My question is, what is the best approach for this when talking about entities properties and also form handling. I've though about two things:
Having 2 fields on the Company entity called PrimaryContact and SecondaryContact and also have a one-to-many relationship to a property called contacts.
What I don't like (or I'm not 100% how to do) about this option is that on the Contact entity I would need an inversedBy field for each of the 2 one-to-one properties and also 1 for the one-to-many relationship and my personal thought is that this is kind of messy for the purpose.
Having a property on the Contact entity called Type which would hold if it's primary, secondary or normal and in the Company methods that has to do with Contacts I would modify it and add the getPrimaryContact, getSecondaryContact, etc.
What I don't like about this option is that I would need to have 2 unmapped properties for the Company and I would need to do a lot on the form types in order to get this to work smoothly.
My question is what is the best approach for this structure and how to deal with forms and these dependencies. Let me know if this is not clear enough and I will take time and preparate an example with code and images.
I'm not yet a Symfony expert but i'm currently learning entites manipulation and relations !
And there is not simple way to do relations with attributes.
You have to create an entity that represent your relation.
Let's suppose you have an entity Company and and entity Contact
Then you will have an entity named CompanyContact whick will represent the relation between your objects. (you can have as many attributes as you wish in your relation entity). (Not sure for the Many-to-One for your case but the idea is the same)
<?php
namespace My\Namespace\Entity
use Doctrine\ORM\Mapping as ORM
/**
* #ORM\Entity(repositoryClass="My\Namespace\Entity\CompanyContactRepository")
*/
class CompanyContact
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="contact_type", type="string", length=255)
*/
private $contactType;
/**
* #ORM\ManyToOne(targetEntity="My\Namespace\Entity\Company")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="My\Namespace\Entity\Contact")
* #ORM\JoinColumn(nullable=false)
*/
private $contact;
}
And in your controller you can do this:
$em = $this->getDoctrine()->getManager();
$company = $em->getRepository('YourBundle:Company')->find($yourCompanyId);
$yourType = "primary";
$companyContacts = $em->getRepository('YourBundle:CompanyContact')
->findBy(array('company' => $company, 'type' => $yourType));
What do you think about this approach ?
If i learn more soon i will get you posted ;)
Thanks to #Cerad this is the following approach I took:
I have a OneToMany property on the Company to hold all the contacts.
Implemented the getPrimaryContact/setPrimaryContact methods and looped through all the contacts and retrieving the one of the type I want. Did the same for the secondary.
On the Form type of the company my issue was that I had the 'mapped' => 'false' option, I removed this since I implemented the getters and setters SF2 knows it has to go to these methods.
`
<?php
namespace XYZ\Entity;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class Company
{
...
/**
* #ORM\OneToMany(targetEntity="\XYZ\Entity\Contact", mappedBy="company", cascade={"persist", "remove"})
*/
private $contacts;
public function getPrimaryContact() { ... }
public function setPrimaryContact(Contact $contact) { //Set the type of $contact and add it $this->addContact($contact) }
public function getSecondaryContact() { ... }
public function setSecondaryContact(Contact $contact) { //Set the type of $contact and add it $this->addContact($contact) }
}`
And for the Form Type I have:
`
class CompanyType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('primaryContact', new ContactType())
->add('secondaryContact', new ContactType())
}
...
}`
With this set everything runs smoothly and I can CRUD without much struggle.

JMSSerializerBundle: specify group per attribute

I'm using Symfony2 and JMSSerializerBundle to build an API. The system that JMSSerializer provides to set different ways of serializing objects using groups is quite useful, however, I'm missing a way to specify which group do you want to serialize in every parameter. Example:
I have an article that is related to a user (author). Articles as well as users can be serialized as "list" or as "details", however, I want the users to be serialized as "list" always that they are retrieved from the article (because "details" group is reserved to be used to fetch the user and just the user). The problem is that if I set the serializer as "details", then the author is also serialized as "details".
In my mind, the code should be something like:
/**
* #var SCA\APIBundle\Entity\User
* #Groups({"list" => "list", "details" => "list"})
*/
private $author;
where the key of the array indicates the way the parent should be serialized, and the value indicates the way the child should be serialized.
Any clue how can I achieve this?
It should not be done on the composed object but on the composition.
In your case, I suppose you have something like that:
class Article
{
/**
* #var User
* #Groups({"list", "details"})
*/
private $author;
}
class User
{
private $firstName;
private $lastName;
}
So if you want to expose the firstName property when serializing the composed object, you need to define the same group in the User object.
It becomes:
class Article
{
/**
* #var User
* #Groups({"list", "details"})
*/
private $author;
}
class User
{
/*
* #Groups({"list"})
*/
private $firstName;
private $lastName;
}
If you need more control, you may define more explicit groups, like "article-list", "user-firstname", "user-list-minimal", etc.
It is up to you to decide the best strategy to adopt.

Accessing relations of a table with inheritance

I have an Inheritance class as shown here:
As you can easily see users, buildings and hotels have addresses (more than one) and address table keeps the id of the owner in whose column.
Is my logic correct?
Let's say I want to get the address of user (or buildings or hotels) whose id is 2; must I run a DQL statement (and how?) or can I get it with find() function without DQL?
And I'll be happy if you give example since Doctrine documentation doesn't help much.
Thanks.
Edit: users, buildings and hotels are just symbolic names that is why they can have multiple addresses otherwise buildings and hotels would have only one address.
Edit 2:I think I couldn't make myself clear, when I talk about the Class Table Inheritance I mean entity class has the Discriminator column as
/**
* ...
*
* #DiscriminatorColumn(name="classname", type="string")
* #DiscriminatorMap({"Entities\users" = "Entities\users",
* "Entities\buildings" = "Entities\buildings"}) ... etc
*/
Each and every subclass is related to parent (Entity) with the foreign key relation as "id". But of course doctrine creates this relation already for me.
Usually an Address is a typical value object. Value objects are usually stored with the entity compositing the value object so it is neither about relations nor about class table inheritance. If your domain indicates otherwise (e.g. you can do something with your address, meaning), they might be an entity, than entity Hotel holds an entity Address (persisted in a n:m relation table) and entity Building holds and Address too (in a different n:m relation table).
If you go the value object route, things are different. You would store the address with the Building entity as well as with the Hotel entity (as you would do it with other value objects may it be Moneyor Email or Password). So you don’t need relations at all, just a few more fields. The issue with Doctrine 2 is, that it does not support Component mapping. Component mapping would be used to nicely store value objects. T accomplish the same thing with Doctrine 2, you would implement a #prePersist and a #postLoad handler like that:
class Hotel
{
private ;
/** These fields are persisted */
/** #Column(type=string) */
private $addressStreet;
/** #Column(type=string) */
private $addressCity;
/** #Column(type=string) */
private $addressZip;
/** #Column(type=string) */
private $addressCountry;
/** #prePersist */
public function serializeValueObjects()
{
$this->addressStreet = ->address->getStreet();
$this->addressCity = ->address->getCity();
$this->addressZip = ->address->getZip();
$this->addressCountry = ->address->getCountry();
}
public function unserializeValueObjects()
{
$this->address = new Address(->addressStreet, ->addressCity, ->addressZip, ->addressCountry);
}
}
As you need to serialize/unserialize Address value objects in various places, you might want to extract the serializing code into a separated class.
/**
*
* #Entity
* #Table(name="proposaltemplate")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="entitytype", type="string")
* #DiscriminatorMap({"proposal" = "ProposalTemplate","page" = "PageTemplate"})
*
*/
abstract class AbstractProposalTemplate
{
/**
*
* #var integer
* #Id
* #Column(type="integer")
* #generatedValue(strategy="AUTO")
*
*/
private $id;
}
next
#Entity
class ProposalTemplate extends AbstractProposalTemplate
{
#Id
#Column(type="integer")
#generatedValue(strategy="AUTO")
private $id;
}
next another class
#Entity
class PageTemplate extends AbstractProposalTemplate
{
/**
*
* #var integer
* #Id
* #Column(type="integer")
* #generatedValue(strategy="AUTO")
*
*/
private $id;
}
So you've got a superclass called "Entity", which has subclasses "User", "Building", and "Hotel".
Your "Entity" entity should have a OneToMany relation to Address. Let's imagine it looks like this, in your Entity definition:
/**
* #OneToMany(targetEntity="Address", mappedBy="whose"
*/
protected $addresses;
This is a more-or-less fine approach, though the use of inheritance is a little smelly.
Then if you want to iterate over the addresses, from inside User, Building, or Hotel:
foreach($this->addresses as $address){
//do something with adderess
}
Does that answer your question?

Categories