How to refer to route name with FOSRestBundle - symfony

Candidate Controller
class DefaultController extends PreviewMeController
{
/**
* Complete registration process for candidate
*
* #ApiDoc(
* section="Candidate",
* tags={"common"},
* )
*
* #Rest\View()
* #Post("/ua/register/candidate/{token}")
*
* #param Request $request
* #return \FOS\RestBundle\View\View
*/
public function registerCandidateAction($token)
{
}
}
Candidate routing.yml
candidate_api_routes:
type: rest
prefix: /v1
resource: "CandidateBundle\Controller\DefaultController"
name_prefix: "api_1_c_"
AppBundle Controller
/**
* Register a new user on the website
*
* #ApiDoc(
* section="Common Functionalities",
* tags={"common"},
* requirements={
* {"name"="email", "dataType"="string", "description"="Email of user"},
* {"name"="username", "dataType"="string", "description"="Username. Keep this same as email address"},
* {"name"="first_name", "dataType"="string", "description"="First name of user"},
* {"name"="last_name", "dataType"="string", "description"="Last name of user"},
* {"name"="plainPassword", "dataType"="array", "requirement"="['first':'password','second':'password']", "description"="Plain password. Send as an array with 'first' and 'second' as array keys"},
* {"name"="user_type","dataType"="string","requirement"="employer|candidate","description"="Employer or candidate user type"}
* },
* statusCodes={
* 200 = "When user is successfully registered",
* 400="When there is a validation error in the registration process"
* }
* )
* #Post("/ua/register")
* #Rest\View()
*
* #param Request $request
* #return array|\FOS\RestBundle\View\View
*/
public function registerAction(Request $request)
{
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.registration.form.factory');
/** #var UserManager $fos_userManager */
$fos_userManager = $this->get('fos_user.user_manager');
/** #var User $user */
$user = $fos_userManager->createUser();
$user->setEnabled(true);
$user->setUserType($request->request->get('user_type'));
//remove user_type from request so it's not forwarded to form
$request->request->remove('user_type');
$form = $formFactory->createForm();
$form->setData($user);
$form->submit($request->request->all());
if( $form->isValid() ){
$event = new UserEvent($user);
$dispatcher = $this->get('event_dispatcher');
$dispatcher->dispatch(PmEvents::REGISTRATION_SUCCESS, $event);
$fos_userManager->updateUser($user);
$wrapper = new PMResponseWrapper();
$wrapper->setData(array(
'ob_key' => $user->getObKey()
));
/** #var View $response */
$response = View::create($wrapper->getFormattedData());
$response->setLocation( $this->generateUrl('register_candidate') );
return $response;
}
return $this->view($form);
}
app/console debug:router dump
api_1_register POST ANY ANY /api/v1/ua/register
api_1_register_confirm_token POST ANY ANY /api/v1/ua/register/confirm_token/{token}
api_1_c_index GET ANY ANY /api/v1/index
api_1_c_register_candidate POST ANY ANY /api/v1/ua/register/candidate/{token}
Problem is even though registerCandidateAction shows up in debug:router, I am not able to call it with $this->generateUrl() in registerAction.
When I call this line $response->setNextUrl($this->generateUrl('register_candidate')); I get this error Unable to generate a URL for the named route \"register_candidate\" as such route does not exist.
Please help in finding what's wrong here.

Related

Symfony 4 - Why does security voter redirect me to the login page when it returns false?

there is something I don't understand in Symfony 4.
I have a custom voter in which I check if I have a property that is true or false:
/**
* Vérifie si les essais gratuits sont activés
*
* #return boolean
*/
public function isFreeTrialEnabled(): bool
{
$stripeParameters = $this->manager->getRepository(StripeParameters::class)->findOneBy([]);
return $stripeParameters->getFreeTrial();
}
In my controller, I've :
/**
* #Route("/trial", name="account_trial")
* #Security("is_granted(constant('\\App\\Security\\Stripe\\Voter\\StripeClientVoter::IS_FREE_TRIAL_ENABLED'))")
*
* #param Request $request
* #param UserPasswordEncoderInterface $encoder
* #param SubscriptionHelper $subscriptionHelper
* #param EntityManagerInterface $manager
* #param StripeClient $stripeClient
* #return Response
*/
public function trialAction(Request $request, UserPasswordEncoderInterface $encoder, SubscriptionHelper $subscriptionHelper , EntityManagerInterface $manager, StripeClient $stripeClient)
{ //... }
So if the vote returns true, it lets me in. If it returns false, it should put an access denied exception.
However, when it returns false to me, it brings me back directly to the login page

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.

Symfony 3 UniqueEntity validation on update

I am facing an issue with UniqueEntity validation.
I have a field "internal_asset_number" which should be unique and it's working fine on create. On update when i edit the existing current data with the same values, it shows "There is already an asset with that internal number!" but it shouldn't because it's the same entry.
The entity:
/**
* Asset
*
* #ORM\Table(schema="assets", name="asset", uniqueConstraints= {#ORM\UniqueConstraint(name="uk_asset_internal_asset_number_client_id", columns={"internal_asset_number", "client_id"})})
* #ORM\Entity(repositoryClass="Api\AssetBundle\Entity\AssetRepository")
* #UniqueEntity(fields={"internalAssetNumber"}, groups={"post", "put"}, message="There is already an asset with that internal number!")
*/
class Asset
{
/**
* #var guid
*
* #ORM\Column(name="id", type="string")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="client_id", type="string", length=255, nullable=false)
*/
private $clientId;
/**
* #var string
*
* #ORM\Column(name="internal_asset_number", type="string", length=255, nullable=true, unique=true)
*/
private $internalAssetNumber;
Update method:
public function putAssetAction(Request $request, $id)
{
$data = $this->deserializer('Api\AssetBundle\Entity\Asset', $request, 'put');
if ($data instanceof \Exception) {
return View::create(['error' => $data->getMessage()], 400);
}
$validator = $this->get('validator');
$errors = $validator->validate($data, null, 'put');
if (count($errors) > 0) {
$errorsResponse = [];
foreach ($errors as $error) {
$errorsResponse = $error->getMessage();
}
return View::create(array('error' => $errorsResponse), 400);
}
...
As #xabbuh commented, the problem is that the entity you persist after update is not retrieved through the entityManager so when you persist it the entity manager thinks it is a new entity.
Here is how to solve it:
$entityManager->merge($entity);
This will tell the entitymanager to merge your serialized entity with the managed one
Some more explanation on merge():
https://stackoverflow.com/a/15838232/5758328

No #QueryParam/#RequestParam configuration for parameter 'username'

Help me please, i create my own annotations extending fosrestbundle annotation, to have url in this format users?param=...&param2=...
my annotation is as follows:
<?php
namespace REST\UserBundle\Annotations;
use FOS\RestBundle\Controller\Annotations\Param;
use Symfony\Component\HttpFoundation\Request;
/**
* #Annotation
* #Target({"CLASS", "METHOD"})
*/
class CustomParam extends Param
{
/**
* {#inheritdoc}
*/
public function getValue(Request $request, $default = null)
{
return $request->request->has($this->getKey())
? $request->request->get($this->getKey(), $default)
: $request->query->get($this->getKey(), $default);
}
}
my controller is as following:
/**
* Create a User from the submitted data.<br/>
*
* #ApiDoc(
* resource = true,
* description = "Creates a new user from the submitted data.",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when the form has errors"
* }
* )
*
* #param ParamFetcher $paramFetcher Paramfetcher
*
* #CustomParam(name="username", value="foufou" ,nullable=false, strict=true, description="Username.")
* #CustomParam(name="email", value="foufou#gmail.com", nullable=false, strict=true, description="Email.")
* #CustomParam(name="password", value="foufou", nullable=false, strict=true, description="Password.")
* #CustomParam(name="plainPassword", value="foufou", nullable=false, strict=true, description="Plain Password.")
* #CustomParam(name="company_name", value="ffff", nullable=false, strict=true, description="Company.")
* #CustomParam(name="adress_zip", value="papappa", nullable=false, strict=true, description="Zip.")
* #CustomParam(name="adress_name", value="rue hhh",nullable=false, strict=true, description="Adress.")
* #CustomParam(name="adress_city", value="paris",nullable=false, strict=true, description="City.")
* #CustomParam(name="adress_country", value="France", nullable=false, strict=true, description="Country.")
*
* #return View
*/
public function postUserAction(ParamFetcher $paramFetcher)
{
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
$user->setUsername($paramFetcher->get('username'));
$user->setEmail($paramFetcher->get('email'));
$user->setPassword($paramFetcher->get('password'));
$user->setPlainPassword($paramFetcher->get('plainPassword'));
$user->setEnabled(true);
$user->addRole('ROLE_API');
$adress = new Adress();
$adress->setAdress($paramFetcher->get('adress_name'));
$adress->setCity($paramFetcher->get('adress_city'));
$adress->setZip($paramFetcher->get('adress_zip'));
$adress->setCountry($paramFetcher->get('adress_country'));
$company = new Company();
$company->setName($paramFetcher->get('company_name'));
$user->setAdress($adress);
$user->setCompany($company);
$view = View::create();
$errors = $this->get('validator')->validate($user, array('Registration'));
if (count($errors) == 0) {
$userManager->updateUser($user);
$view->setData($user)->setStatusCode(200);
return $view;
} else {
$view = $this->getErrorsView($errors);
return $view;
}
}
but i get this error, No #QueryParam/#RequestParam configuration for parameter 'username'

'Circular reference has been detected' error when serializing many-to-many-associated objects

Since upgrading to Symfony 2.7, I seem to keep getting 'circular reference has been detected' errors when attempting to serialize an array of contacts associated with a given group. They're setup in a many-to-many association (one group has many contacts; one contact has many group-associations).
Now, I followed the guide for using serialization groups as per the 2.7 upgrade post, but still seem to get the error. My controller code for this is currently as follows:
$group = $this->getDoctrine()
->getRepository('TwbGroupsBundle:ContactGroup')
->find($id);
$groupContacts = $group->getContacts();
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$json = $serializer->serialize($groupContacts, 'json', array(
'groups' => array('public')
));
When running $serializer->serialize(), I get the CircularReferenceException after 1 circular reference. So far I have my Contact entity configured like so, with the #Groups annotations:
/**
* Contact
*
* #ORM\Table(name="tblContacts")
* #ORM\Entity(repositoryClass="Twb\Bundle\ContactsBundle\Entity\Repository\ContactRepository")
*/
class Contact implements ContactInterface
{
/**
* #var string
*
* #ORM\Column(name="ContactName", type="string", length=50, nullable=true)
* #Groups({"public"})
*/
private $contactname;
/**
* #var integer
*
* #ORM\Column(name="ContactID", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #Groups({"public"})
*/
private $contactid;
/**
*
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Twb\Bundle\GroupsBundle\Entity\ContactGroup", inversedBy="contacts")
* #ORM\JoinTable(name="tblContactsGroupsAssignments",
* joinColumns={#ORM\JoinColumn(name="contactId", referencedColumnName="ContactID")},
* inverseJoinColumns={#ORM\JoinColumn(name="contactGroupId", referencedColumnName="id")}
* )
*/
protected $contactGroups;
// ...getters/setters and so on
}
And my ContactGroup entity:
/**
* ContactGroup
*
* #ORM\Table(name="tblContactsGroups")
* #ORM\Entity
*/
class ContactGroup
{
// ...
/**
*
* #var Contact
*
* #ORM\ManyToMany(targetEntity="Twb\Bundle\ContactsBundle\Entity\Contact", mappedBy="contactGroups")
*/
private $contacts;
// ...
}
Is there something I'm missing here to get around the circularity problem? Many thanks.
It looks like something wrong with config.
You have to enable serialization groups annotation:
# app/config/config.yml
framework:
# ...
serializer:
enable_annotations: true
And proper use statement has to be present in ContactGroup entity class
use Symfony\Component\Serializer\Annotation\Groups;
$normalizers->setCircularReferenceHandler(function ($object) {
return $object->getId();
});
Just add it after you make the instance of your objectNormalizer ;

Resources