JMS Serializer add groups by some condition - symfony

I have entity Notification and when my entity contain some condition I want add additional groups for serializer, I create event serializer.pre_serialize for Notification and after check condition add additional groups in serialized context, but in response it's not works. Example I want add some nested to my Notification it would be some another Entity, example Question and this entity have some groups, and after set it when I create context SerializationContext::create() everything work, but when I try add additional group in serializer.pre_serialize it's not work
this my entity
class Notifications
{
use TraitTimestampable;
const GROUP_POST_NOTIFICATION = 'post_notifications';
const GROUP_GET_NOTIFICATIONS = 'get_notifications';
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Annotation\Groups({
* "get_notifications"
* })
*/
private $id;
/**
* #var object
*
* #Annotation\Groups({
* "get_notifications"
* })
*/
private $providerEntity;
and another entity which I adding in event
class Questions implements NotificationInterface
{
use TraitTimestampable;
const GROUP_POST = 'post_question';
const GROUP_GET = 'get_question';
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Annotation\Groups({
* "get_question"
* })
*/
private $id;
/**
* #return array
*/
public static function getGetGroup()
{
return [self::GROUP_GET];
}
but in response I have
public function onPreSerializeNotifications(PreSerializeEvent $event)
{
/** #var Notifications $notifications*/
$notifications = $event->getObject();
$provider = $notifications->getProvider(); //this is Questions::class
$this->providerEntity = $this->entityManager->getRepository($provider)
->findOneBy(['id' => $notifications->getProviderId()]);
$notifications->setProviderEntity($this->providerEntity);
$attr = $event->getContext()->attributes;
$groups = array_merge($attr->all()['groups'], $provider::getGetGroup());
$attr->set('groups', $groups);
}
but in response I have
"notifications": [
{
"id": 2,
"provider_entity": {}
}
]
why ?

Related

How can I serialize only the ID of a child

I'm looking for the correct way to serialize a child of my object.
I have the following classes:
class company {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups ({"get"})
*/
private $id;
}
class User {
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Groups ({"get"})
*/
private $email;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="users")
* #ORM\JoinColumn(nullable=false)
* #Groups ({"get"})
*/
private $company;
}
As soon as I use the serializer on a User object, I receive the following response.
{
"id": 1,
"email": "email#mydomain.com",
"company": {
"id": 1
}
}
But I prefer the following response, how can I get these?
{
"id": 1,
"email": "email#mydomain.com",
"company": 1
}
You can create a custom normalizer, and summarise the company within the User - as it is being converted from the original object (with sub-entities) to an array, before being json-encoded.
symfony console make:serializer:normalizer [optional name, eg: 'UserNormalizer']
This creates a new class, with in part, the contents:
public function normalize($object, $format = null, array $context = []): array
{
// $object is a User entity at this point
$data = $this->normalizer->normalize($object, $format, $context);
// Here: add, edit, or delete some data
// and we summarise the company entity to just the ID.
$data['company'] = $object->getCompany()->getId();
return $data;
}
When I did this with a slightly more complex entity that referred back to the original one (if company had a reference back to a user), it made 'A circular reference', so I added an annotation to #Ignore the field in the User entity, for serialization purposes. It was still given to the normalizer, to use from the object passed into normalize().
You could also serialize the result of a method instead:
class User {
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Groups ({"get"})
*/
private $email;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="users")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #Groups ({"get"})
*/
public function getCompanyId()
{
return $this->company->getId();
}
}

Is there a way to specify the value of a foreign key without any reference to the entity object or repositories in Doctrine

I have 2 Entities.
Part and Inventory
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $partNumber;
/** #ORM\Column(name="part_name", type="string") */
private $partName;
/** #ORM\Column(type="string") */
private $warehouseStatus;
....
Inventory.php
class Inventory
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One Product has One Shipment.
* #ORM\OneToOne(targetEntity="Part")
* #ORM\JoinColumn(name="part_number", referencedColumnName="part_number")
*/
private $partNumber;
/** #ORM\Column(type="decimal") */
private $inStock;
I create the Part in this way
class one {
private function method1 {
$part = new Part();
$part->partNumber = 'blabla';
$part->warehouseStatus = 1;
.....
}
class two {
private function method1 {
$inv = new Inventory();
$inv->partNumber = 'blabla'; // it crashes here
$inv->inStock = 1;
.....
}
}
In class two I'm trying to make a relation with the first object but partNumber crashes since he is expecting an Entity Object as Part and not a string. Is there an integrated doctrine method to create a reference to the part entity without having to instantiate repositories and so forth.
You need to use the getReference function from the EntityManager for that:
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* #param string $entityName The name of the entity type.
* #param mixed $id The entity identifier.
*
* #return object|null The entity reference.
*
* #throws ORMException
*/
public function getReference($entityName, $id);
In your case:
$inv->partNumber = $entityManager->getReference(Part::class, $thePartIdYouReference);

Override Method or EventListener: stop creation process and show warning just the first time in EasyAdmin?

I am using EasyAdmin in my SF 3.3 project but I need to achieve something different from how EasyAdmin has been built for. Take a look at the following picture:
As you might notice a user can be in more than one GroupingRole. Having that information the challenge is:
Check if the user has been assigned to any other GroupingRole
If the criteria meets the condition then show a warning message saying "The user A is already assigned to GroupingRole A" and prevent the record to be created. (this message could be in a popup, a javascript alert or an alert from Bootstrap - since EA already uses it)
When the admin click once again on "Save changes" the record should be created.
What I want to achieve with this approach is to alert the admin that the user is already to any other group but not stop him for create the record.
I have achieve some part of it already by override the prePersist method for just that entity (see below):
class AdminController extends BaseAdminController
{
/**
* Check if the users has been assigned to any group
*/
protected function prePersistGroupingRoleEntity($entity)
{
$usersToGroupRoleEntities = $this->em->getRepository('CommonBundle:UsersToGroupRole')->findAll();
$usersToGroupRole = [];
/** #var UsersToGroupRole $groupRole */
foreach ($usersToGroupRoleEntities as $groupRole) {
$usersToGroupRole[$groupRole->getGroupingRoleId()][] = $groupRole->getUsersId();
}
$usersInGroup = [];
/** #var Users $userEntity */
foreach ($entity->getUsersInGroup() as $userEntity) {
foreach ($usersToGroupRole as $group => $users) {
if (\in_array($userEntity->getId(), $users, true)) {
$usersInGroup[$group][] = $userEntity->getId();
}
}
}
$groupingRoleEnt = $this->em->getRepository('CommonBundle:GroupingRole');
$usersEnt = $this->em->getRepository('CommonBundle:Users');
$message = [];
foreach ($usersInGroup as $group => $user) {
foreach($user as $usr) {
$message[] = sprintf(
'The user %s already exists in %s group!',
$usersEnt->find($usr)->getEmail(),
$groupingRoleEnt->find($group)->getName()
);
}
}
}
}
What I don't know is how to stop the record to be created and instead show the warning just the first time the button is clicked because the second time and having the warning in place I should allow to create the record.
Can any give me some ideas and/or suggestions?
UPDATE: adding entities information
In addition to the code displayed above here is the entities involved in such process:
/**
* #ORM\Entity
* #ORM\Table(name="grouping_role")
*/
class GroupingRole
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer",unique=true,nullable=false)
* #ORM\GeneratedValue
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="role_name", type="string", nullable=false)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="role_description", type="string", nullable=false)
*/
private $description;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Schneider\QuoteBundle\Entity\Distributor", inversedBy="groupingRole")
* #ORM\JoinTable(name="grouping_to_role",
* joinColumns={
* #ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="DistributorID", referencedColumnName="DistributorID", nullable=false)
* }
* )
*
* #Assert\Count(
* min = 1,
* minMessage = "You must select at least one Distributor"
* )
*/
private $distributorGroup;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="CommonBundle\Entity\Users", inversedBy="usersGroup")
* #ORM\JoinTable(name="users_to_group_role",
* joinColumns={
* #ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="users_id", referencedColumnName="users_id", nullable=false)
* }
* )
*
* #Assert\Count(
* min = 1,
* minMessage = "You must select at least one user"
* )
*/
private $usersInGroup;
/**
* Constructor
*/
public function __construct()
{
$this->distributorGroup = new ArrayCollection();
$this->usersInGroup = new ArrayCollection();
}
}
/**
* #ORM\Entity()
* #ORM\Table(name="users_to_group_role")
*/
class UsersToGroupRole
{
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(type="integer",nullable=false)
* #Assert\Type(type="integer")
* #Assert\NotNull()
*/
protected $usersId;
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(type="integer", nullable=false)
* #Assert\Type(type="integer")
* #Assert\NotNull()
*/
protected $groupingRoleId;
}
A little example by using form validation approach in EasyAdminBundle:
class AdminController extends EasyAdminController
{
// ...
protected function create<EntityName>EntityFormBuilder($entity, $view)
{
$builder = parent::createEntityFormBuilder($entity, $view);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$flag = false;
if (isset($data['flag'])) {
$flag = $data['flag'];
unset($data['flag']);
}
$key = md5(json_encode($data));
if ($flag !== $key) {
$event->getForm()->add('flag', HiddenType::class, ['mapped' => false]);
$data['flag'] = $key;
$event->setData($data);
}
});
return $builder;
}
protected function get<EntityName>EntityFormOptions($entity, $view)
{
$options = parent::getEntityFormOptions($entity, $view);
$options['validation_groups'] = function (FormInterface $form) {
if ($form->has('flag')) {
return ['Default', 'CheckUserGroup'];
}
return ['Default'];
};
$options['constraints'] = new Callback([
'callback' => function($entity, ExecutionContextInterface $context) {
// validate here and adds the violation if applicable.
$context->buildViolation('Warning!')
->atPath('<field>')
->addViolation();
},
'groups' => 'CheckUserGroup',
]);
return $options;
}
}
Note that PRE_SUBMIT event is triggered before the validation process happen.
The flag field is added (dynamically) the first time upon submitted the form, so the validation group CheckUserGroup is added and the callback constraint do its job. Later, the second time the submitted data contains the flag hash (if the data does not changes) the flag field is not added, so the validation group is not added either and the entity is saved (same if the callback constraint does not add the violation the first time).
Also (if you prefer) you can do all this inside a custom form type for the target entity.

POST request on many-to-many association

I have two entities with a many-to-many association:
class User extends BaseUser
and
class Calendar
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="text")
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="view", type="text")
* #Assert\Choice(choices = {"work week", "week", "month", "year"}, message = "Choose a valid view.")
*/
private $view;
/**
* #ManyToMany(targetEntity="AppBundle\Entity\User")
* #JoinTable(name="calendars_publishers",
* joinColumns={#JoinColumn(name="calendar_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="publisher_id", referencedColumnName="id", unique=true)}
* )
*/
private $publishers;
public function __construct()
{
$this->publishers = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add publishers
*
* #param \AppBundle\Entity\User $publishers
* #return Calendar
*/
public function addPublisher(\AppBundle\Entity\User $publishers)
{
$this->publishers[] = $publishers;
return $this;
}
/**
* Remove publishers
*
* #param \AppBundle\Entity\User $publishers
*/
public function removePublisher(\AppBundle\Entity\User $publishers)
{
$this->publishers->removeElement($publishers);
}
}
And when I do a POST request on my REST API (http://localhost:8000/calendars) with the body:
{
"name": "My calendar",
"view": "week",
"publishers": [
"/users/1",
"/users/2"
]
}
I have the response:
{
"#context": "/contexts/Calendar",
"#id": "/calendars/3",
"#type": "Calendar",
"name": "My calendar",
"view": "week",
"publishers": []
}
So, as you see, my object is recorded well, but I cannot put some users in publishers. Do you have an idea?
I use the bundles:
DunglasApiBundle
NelmioApiDocBundle
NelmioCorsBundle
FOSHttpCacheBundle
FOSUserBundle
LexikJWTAuthenticationBundle
with api-platform.
You should add addPublisher(User $publisher) and removePublisher(User $publisher) methods.
API Platform internally uses the Symfony PropertyAccess component, and this component requires such methods to be able to access to the private property.
It sounds like you should specify fetch="EAGER" in the relationship so that it always gets the related objects.
see http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#manytoone
(and also the next paragraph)
and some good info here: http://www.uvd.co.uk/blog/some-doctrine-2-best-practices/

Doctrine2 How to check if there is the related record in

I have a bidirectional many-to-many between theese two entities:
Position
/**
* Position
*
* #ORM\Table(name="applypie_position")
* #ORM\Entity(repositoryClass="Applypie\Bundle\PositionBundle\Entity\PositionRepository")
*/
class Position
{
const IS_ACTIVE = true;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\UserBundle\Entity\Applicant", mappedBy="bookmarks")
*/
private $bookmarkedApplicants;
Applicant
/**
* Applicant
*
* #ORM\Table(name="applypie_applicant")
* #ORM\Entity
*/
class Applicant
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Applypie\Bundle\PositionBundle\Entity\Position", inversedBy="bookmarkedApplicants")
* #ORM\JoinTable(name="applypie_user_job_bookmarks",
* joinColumns={#ORM\JoinColumn(name="applicant_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="position_id", referencedColumnName="id")}
* )
*/
private $bookmarks;
My Problem is: In an PositionControllers Action which easily shows an position by ID i need to know if there the current Applicant whichs wants to see the position has an bookmark for the current position.
I first thought of get all the Bookmarks with $applicant->getBookmarks() and run in within a forearch, checking all the applicants bookmarks against the current position, but i think there must be an easier way?
thank you
If you want to stay object oriented you can do it this way:
class Applicant
{
// fields and ORM annotations
public function hasBookmark(Bookmark $bookmark) {
return $this->bookmarks->contains($bookmark);
}
class MyController
{
public function testAction() {
$applicant = $this->getUser(); // or however you fetch the applicant object
$bookmark = $bookmarkRepository->find($bookmarkId); // again, however you get the bookmark object
// #var boolean $applicantHasBookmark
$applicantHasBookmark = $applicant->hasBookmark($bookmark);
// other controller code
}

Resources