I want to update values in my Entity using PATCH method, but when I try to do that, doctrine create new record in table, but do not update. For example I want to update name attribute, so I send this JSON array (in url I send id of the record: api.test/item/{id}):
{
"name": "newname"
}
My Controller:
public function updateItemAction(Request $request, $id)
{
$serializer = $this->get('jms_serializer');
$content = $request->getContent();
$item = $serializer->deserialize($content,Item::class,'json');
$em = $this->getDoctrine()->getManager();
$em->getRepository(Item::class)->find($id);
$em->persist($item);
$em->flush();
return new View("updated!",Response::HTTP_OK);
}
You must withdraw the persist method from your code and it will work better.
Have you tried using the merge() function? This allows you to merge the entity into the database, updating the existing entity. Call this function instead of persist. Here is a guide
https://www.vivait.co.uk/labs/updating-entities-when-an-insert-has-a-duplicate-key-in-doctrine
Related
I am running a Symfony 2.8 based web app using FOSUserBundle to manage users. Creating new users with a web form is absolutely no problem.
Now I would like to add a feature to create new users with a REST api. Of course submitting username, password, email, etc. to a controller is no big deal:
public function registerAction(Request $request) {
$requestJson = json_decode($request->getContent(), true);
$username = $requestJson[JSON_KEY_USERNAME];
$email = $requestJson[JSON_KEY_MAIL];
$password = $requestJson[JSON_KEY_PASSWORD];
...
$this->registerUser($username, $email, $password);
...
}
private function registerUser($username, $email, $password, $locale, $timezone, $currency) {
$userManager = $this->get('fos_user.user_manager');
$emailExist = $userManager->findUserByEmail($email);
$userNameExists = $userManager->findUserByUsername($username);
if ($emailExist || $userNameExists)
return false;
$user = $userManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
...
$user->setLocked(0);
$user->setEnabled(0);
$userManager->updateUser($user);
return true;
}
However, this performs no validation at all. If for example the username is empty an NotNullConstraintViolationException is thrown when persisting the user object.
Of course I could manually re-implement the same validation process which is used by the RegistrationForm (username not empty, not taken, no invalid characters, password min length, e-mail format, etc.) and pass back the same error messages but this would mean to reinvent the wheel.
Is it somehow possible to run the exact same validation which is used by the RegistrationForm?
Symfony validator can work independently. In a controller you can use validator service like this:
$violations = $this->get('validator')->validate($user, null, ['your_groups_here']);
// Or inject Symfony\Component\Validator\Validator\ValidatorInterface into a service.
It will return a ConstraintViolationListInterface, you can loop trough this object.
You can check FOSUserBundle validation groups here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/config/validation.xml
I would like to pass the authenticated users list of roles to my front end apps, so I can use the same access control structure in the front and back end.
I was looking in the security / authentication classes as that is where the isGranted function are for me to do this
$this->container->get('security.context')->isGranted('ROLE_SUPER_ADMIN')
I can't find anything to get a list of roles though, is this not a supported feature?
nb: I don't want the entire role hierarchy, just the list of roles for the authenticated user
I ended up adding a new repository function and a service method to get this info.
MyProject/UserBundle/Entity/Repository/UserRepository
public function getRoles($userId)
{
$queryBuilder = $this->createQueryBuilder('u');
$queryBuilder
->select('u.id, u.roles AS user_roles, g.roles AS group_roles')
->leftJoin('u.groups', 'g')
->andWhere('u.id = :user_id')
->setParameter('user_id', $userId);
return $queryBuilder->getQuery()->getArrayResult();
}
MyProject/UserBundle/Service/UserService
public function getUserRoles($user)
{
$groupRoles = $this->repository->getRoles($user->getId());
$roles = array('user_roles' => array(), 'group_roles' => array());
foreach ($groupRoles as $groupRole) {
$roles['user_roles'] = array_merge($roles['user_roles'], $groupRole['user_roles']);
$roles['group_roles'] = array_merge($roles['group_roles'], $groupRole['group_roles']);
}
return $roles;
}
This gives me an array like this
"roles":{
"user_roles":[],
"group_roles":["ROLE_ADMIN","ROLE_ONE","ROLE_TWO","ROLE_BEST"]
}
Assuming you're using the Symfony security component, the user interface which your user class implements has this already included:
$user = $this->get('security.token_storage')->getToken()->getUser();
var_dump($user->getRoles());
http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getRoles
I am trying to add error to form using FormError. Error must be displayed when user tries to create collection with existing name. But this code doesn't work, and I can't understand why
public function submitInObjectAction(Request $request)
{
$collection = new Collection();
$user = $this->getUser();
$form = $this->createForm(
new CollectionType(),
$collection
);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
$colname = $form["name"]->getData();
$existing = $this->getDoctrine()->getRepository('CollectionBundle:Collection')
->findBy(['name' => $colname, 'user' => $user]);
if ($existing != NULL) {
$error = new FormError("You already have collection with such name");
$form->get('name')->addError($error);
}
$em = $this->getDoctrine()->getManager();
$collection->setUser($user);
$em->persist($collection);
$em->flush();
return new JsonResponse([
'id' => $collection->getId(),
'name' => $collection->getName()
]);
}
}
I cannot use annotation on name field in Collection entity, because names must be unique only for particular user
I think it is too late in the chain. Form validation happens when you call $form->handleRequest() and by the time $form->isValid() is called your validation should be complete. It is better to add validation constraints further up the chain. See the Symfony guide on form validation and if necessary the validation component, for more info.
I would use annotations to set a unique constraint on the name field of the Collection entity in the CollectionBundle.
This not only validates this user input form, but any other form or component or bundle which uses CollectionBundle - and Doctrine will even prevent storage depending on the constraint leaving your database tidy!
EDIT: Another option for more advanced validation is writing a custom form event listener. Three events are dispatched when Form::handleRequest() or Form::submit() are called: FormEvents::PRE_SUBMIT, FormEvents::SUBMIT, FormEvents::POST_SUBMIT. This example also shows how to access the form itself.
$form = $formFactory->createBuilder()
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$user = $event->getData();
$form = $event->getForm();
// .. validation here
})
->getForm();
How do I create and access Symfony 2 session variables in my controllers.
I used like this.
$session = new Session();
$session->start();
$session->set('loginUserId',$user['user_id']);
I want to know how to use the above session variable in all my controllers to access.
One way of using Sessions in Symfony in controller is:
setting:
$this->get('session')->set('loginUserId', $user['user_id']);
getting:
$this->get('session')->get('loginUserId');
If you use standard framework edition
From the docs:
Symfony sessions are designed to replace several native PHP functions.
Applications should avoid using session_start(),
session_regenerate_id(), session_id(), session_name(), and
session_destroy() and instead use the APIs in the following section.
and:
While it is recommended to explicitly start a session, a sessions will
actually start on demand, that is, if any session request is made to
read/write session data.
So sessions is started automatically and can be accessed e.g. from controllers via:
public function indexAction(Request $request)
{
$session = $request->getSession();
...
}
or:
public function indexAction()
{
$session = $this->getRequest()->getSession();
// or
$session = $this->get('session');
...
}
than:
// store an attribute for reuse during a later user request
$session->set('foo', 'bar');
// get the attribute set by another controller in another request
$foobar = $session->get('foobar');
// use a default value if the attribute doesn't exist
$filters = $session->get('filters', array());
http://symfony.com/doc/current/components/http_foundation/sessions.html
use Symfony\Component\HttpFoundation\Session\Session;
$session = new Session();
$session->start();
// set and get session attributes
$session->set('name', 'Drak');
$session->get('name');
// set flash messages
$session->getFlashBag()->add('notice', 'Profile updated');
// retrieve messages
foreach ($session->getFlashBag()->get('notice', array()) as $message) {
echo '<div class="flash-notice">'.$message.'</div>';
}
I have to develop simple mail client in symfony2 using IMAP. Im wondering what is best way to retrieve messages from server (lets take a gmail as example)?
I did something like this:
public function indexAction($name)
{
$user = 'adress#gmail.com';
$password = 'password';
$mailbox = "{imap.gmail.com:993/imap/ssl}INBOX";
$mbx = imap_open($mailbox , $user , $password);
$ck = imap_check($mbx);
$mails = imap_fetch_overview($mbx,"1:5");
return $this->render('HtstMailBundle:Mail:index.html.twig',array('name'=>$name,'mail'=>$mails));
}
is this right way, or not? It works, but is it compatible with symfony "standards"?
This has nothing to do with symfony "standards". But you can make your code more OOP if you move all login to a service class and use symfony DepencyInjection to create and get your service:
public function indexAction($name)
{
$user = 'adress#gmail.com';
$password = 'password';
$mailbox = "{imap.gmail.com:993/imap/ssl}INBOX";
$mails = $this->get("mail.checker")->receive($user, $password, $mailbox);
return $this->render('HtstMailBundle:Mail:index.html.twig',array('name'=>$name,'mail'=>$mails));
}
Class declaration:
class MailChecker
{
public function receive($user, $password, $mailbox)
{
...imap_check()...
}
}
service declartion:
services:
mail.checker:
class: Project\YourBundle\Service\MailChecker
You can also use this Symfony bundle for that and use it as a service. I is designed for old Symfony2 but tested it with Symfony 3 and works :)