How to use the User value resolver in Symfony 3.2? - symfony

I'm building a small website using symfony 3.2.
There is a page in which the user can change its profile data with a form. I used the structure seen in the official tutorial. Here is the declaration of my controller :
/** #Route("/profil", name="profil_show_user") */
public function userProfileAction(Request $request, UserInterface $user) {
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
//...
$user->getProfile();//and do stuff
//...
}
My problem is that if the user is disconnected for being inactive for too long, or if someone bookmark the page but is not connected, I have this ugly error coming :
Controller
"AppBundle\Controller\ProfilController::userProfileAction()" requires
that you provide a value for the "$user" argument. Either the argument
is nullable and no null value has been provided, no default value has
been provided or because there is a non optional argument after this
one. 500 Internal Server Error - RuntimeException
In the New in Symfony 3.2 changelog, there is something about the new User value resolver. I tried to change to UserInterface $user = null, and it make the page redirect to the path I set in failure_path of security.yml, which is the good behaviour.
But then if I'm connected and go to profil_show_user, I get that other error :
Error: Call to a member function getProfile() on null
I search thoroughly the symfony documentation but couldn't find anything.
Could someone explain to me what goes wrong, what I misunderstood and how can I make this work ?
EDIT :
I thought I might say that if I don't use te value resolver, everything works fine. This is an educationnal and curiosity question about a new feature which I don't manage to use. This code works :
/** #Route("/profil", name="profil_show_user") */
public function userProfileAction(Request $request) {
$user = $this->getUser();
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
//...
$user->getProfile();//and do stuff
//...
}

Related

How to pass phpStan with a customLoginLinkNotification?

I'm using the login_link (docs) to authenticate an user who forgot his password.
Following the docs, I customized the email and I override the htmlTemplate but in real life phpStan stop me saying :
Call to an undefined method Symfony\Component\Mime\RawMessage::htmlTemplate().
I'm trying to fix it but I'm stuck :(
Any idea ?
The return type hint for EmailMessage::getMessage() is RawMessage, which in fact has no methode htmlTemplate().
However in this particular case, you are actually getting a NotificationMail (see the related code here) which eventually inherits from the RawMessage but implements the htmlTemplate() method (or, to be more precises, it inherits from the TemplatedEmail which implements said method).
There are a couple of ways to fix this issue with phpstan:
Check if $email is in fact an instance of NotificationMail
$email = $emailMessage->getMessage();
if ($email instanceof NotificationMail) {
$email->htmlTemplate(...);
}
Add an assert which will throw an exception during runtime if $email is not a NotificationMail
$email = $emailMessage->getMessage();
assert($email instanceof NotificationMail);
$email->htmlTemplate(...);
Add an inline #var. Note that this method is discouraged as per the phpstan documentation. (Thanks to #Ondřej Mirtes for pointing that out)
/** #var NotificationMail $email */
$email = $emailMessage->getMessage();

Symfony login form redirect according to user role

I've used the maker bundle to create a standard login form. When the user has successfully logged in it calls function onAuthenticationSuccess to redirect to the new page.
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
However, I would like to redirect to different pages depending on what role the user has. I would like to do something like:
if ($this->security->isGranted('ROLE_STANDARD_USER')) {
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
if ($this->security->isGranted('ROLE_SYS_ADMIN')) {
return new RedirectResponse($this->urlGenerator->generate('app_ADMINpage'));
}
But the error I'm getting is Undefined property: App\Security\LoginFormAuthenticator::$security
Many thanks in advance for the help.
I didn't have much to go by, but it seems that the default implementation of the LoginFormAuthenticator does not request the AuthorizationChecker.
You can fix this by injecting an AuthorizationChecker into your class using the constructor. Example of how dependency injection works
Seeing as you have used the maker bundle, it is safe to assume you have autowiring turned on for your services, meaning that the Symfony kernel will automagically do all the rest of the work

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

Symfony2-entityManager inside an Entity

I am using Symfony version 2.7.6. I have created an entity named EmployeeBasicInfo having fields
firstname
lastname
identificationCode etc
I have created a callback function for validating Identification code in EmployeeBasicInfo entity itself which looks like
/**
* #Assert\Callback(groups={"edit_myinfo"})
*/
public function validateIdentificationCode(ExecutionContextInterface $context)
{
if ($this->getEmployeeFirstName() == 'fakename') {
$context->buildViolation('This name sounds totally fake!')
->atPath('employeeFirstName')
->addViolation();
}
}
and this callback function works properly
Actually I want such a callback functionality which checks identidfication code against database. I have added $em = $this->getDoctrine()->getManager(); inside the callback function and the error is like Attempted to call an undefined method named "getDoctrine" of class "XXX\EmployeeBundle\Entity\EmployeeBasicInfo".. Please advise me the effective way
Do not inject the EntityManager in your Entity. One basic concept of the DataMapper-Pattern is, that your entity does not have to know about your data source and its connectors.
I'd suggest to write a custom validation constraint, in which you inject the dependencies you need.
EntityManager, Repository to query, etc. Whatever service suits you.
Have a look at how to create custom constraint validators with dependencies
I would suggest you use a service to do this
class EmployeeUtility($connection)
{
public function __construct($conn) { $this->connection = $v; }
public function validateIdentificationCode($emloyeeId, $validationCode)
{
// Your code here
}
}
In your controller, you inject the service:
$employeeUtility = $this->get('employee.utility');
$employeeUtility->validateIdentificationCode(1,'GF38883dkDdW3373d');
Alternatively, add the code in a repository class.

Sonata Admin - how to set the menu.label attribute?

According to the Sonata source code, the last node in the breadcrumb is rendered this way:
# standard_layout.html.twig #
<li class="active"><span>{{ menu.label }}</span></li>
In my setup, when opening a given Admin subclass, the last node simply becomes a raw string according to the entity handled by the Admin:
Dashboard / Entity List / Acme\SomeBundle\Entity\Stuff:000000001d74ac0a00007ff2930a326f
How can I set the value of menu.label to get something more appropriate? I have tried, in my Admin subclass, to override the following:
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$this->configureSideMenu($menu, $action, $childAdmin);
}
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$menu->setLabel("Some nice label");
$menu->setName("Some nice name");
}
However, this does not change anything, even though I have verified that the methods above are called during runtime.
Finally found a good (and somewhat obvious) solution to this.
The Sonata Admin class uses an internal toString($object) method in order to get a label string for the entity it is handling. Thus, the key is to implement the __toString() method of the entity in question:
public function __toString() {
return "test";
}
The best way is to configure the $classnameLabel variable in the Admin Class :
class fooAdmin extends Admin
{
protected $classnameLabel = 'Custom Label';
}
But it have the same issue (weird name with entity path) doing it, even if it is working fine on all the others pages.
Apparently, the Sonata way of solving this is show here:
Quote:
While it’s very friendly of the SonataAdminBundle to notify the admin of a successful creation, the classname and some sort of hash aren’t really nice to read. This is the default string representation of an object in the SonataAdminBundle. You can change it by defining a toString() (note: no underscore prefix) method in the Admin class. This receives the object to transform to a string as the first parameter:
Source: https://sonata-project.org/bundles/admin/master/doc/getting_started/the_form_view.html#creating-a-blog-post

Resources