Property is not flushed - symfony

I call a service in a controller:
$this->getContainer()->get('pro_convocation')->sendDelegationEmails();
Here it is the method executed with the service:
function sendDelegationEmails()
{
foreach ($this->getRepository()->findWithPendingDelegationsEmail(1) as $convocation) {
if (! $convocation->delegationsEmailCanBeSent()) continue;
$this->sendDelegationsEmails($convocation);
$convocation->_setDelegationsEmailIsSent(); //<--- it is not saved
$this->save($convocation);
}
}
and the other used methods in the same class:
/**
* #return \Pro\ConvocationBundle\Entity\ConvocationRepository
*/
private function getRepository()
{
return $this->get('doctrine')->getRepository('ProConvocationBundle:Convocation');
}
function save(ConvocationEntity $convocation)
{
$this->getEntityManager()->persist($convocation);
$this->getEntityManager()->flush();
}
function sendDefinitiveMinute(ConvocationEntity $convocation)
{
foreach ($convocation->getCommunity()->getMembers() as $user) {
$this->get('pro.notifications')->send(Notification::create()
...
->setBody($this->get('templating')->render(
'ProConvocationBundle:Convocation:definitive-minute.html.twig',
array(
'convocation' => $convocation,
'convocationIsOrdinary' => $this->get('pro_convocation.ordinariness')->isOrdinary($convocation),
'user' => $user
)
))
);
}
}
EDIT: The send method:
function send(Notification $notification)
{
if (! $notification->getRecipient()) throw new \Exception("Recipient not set");
if (! $notification->getRecipient()->getEmail()) {
$this->logger->info(sprintf(
"Notification \"%s\" ignored as recipient %s has no email",
$notification->getSubject(), $notification->getRecipient()));
return;
}
// Ignore notifications to the own author
if ($notification->getAuthor() === $notification->getRecipient()) {
$this->logger->info(sprintf(
"Notification ignored as recipient is the author (%s)",
$notification->getRecipient()));
return;
}
$em = $this->doctrine->getManager();
$em->persist($notification);
$em->flush();
if ($this->notificationHasToBeMailed($notification)) {
$this->mail($notification);
$this->logger->info("Notification mailed to {$notification->getRecipient()}");
}
}
END EDIT
The _setDelegationsEmailIsSent method and delegationsEmailIsSent property in the Convocation entity:
/**
* #ORM\Column(type="boolean", name="delegations_email_is_sent")
* #Constraints\NotNull
*/
private $delegationsEmailIsSent = false;
/**
* #internal
* #see \Pro\ConvocationBundle\Convocation::sendDelegationsEmails()
*/
function _setDelegationsEmailIsSent()
{
$this->delegationsEmailIsSent = true;
}
The problem is that delegations_email_is_sent in the database is not changing to true when executing the sendDelegationEmails method in a controller. I've tried several changes without success. It seems to be related to flush method, but I don't find the issue.

Related

Voter classes in Symfony 4

I'm taking over someone's code and I don't understand something about the voting.
Here is the PhotosController class:
class PhotosController extends Controller
{
/**
* #Route("/dashboard/photos/{id}/view", name="dashboard_photos_view")
* #Security("is_granted('view.photo', photo)")
* #param Photo $photo
* #param PhotoRepository $photoRepository
*/
public function index(Photo $photo, PhotoRepository $photoRepository)
{
$obj = $photoRepository->getFileObjectFromS3($photo);
header("Content-Type: {$obj['ContentType']}");
echo $obj['Body'];
exit;
}
Here is the voter class:
class PhotoVoter extends Voter
{
const VIEW = 'view.photo';
protected function supports($attribute, $subject)
{
if (!$subject instanceof Photo) {
return false;
}
if (!in_array($attribute, array(self::VIEW))) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $subject->getUser()->getId() === $token->getUser()->getId();
}
}
I don't understand what the
, photo
is for in the PhotosController class. And in PhpStorm I get "cannot find declaration" when I try to go to the "is_granted" declaration.

#Security("is_granted('remove', user)") uses not request user, symfony2

When I use the annotation #Security("is_granted('remove', user)"), I get a wrong user in Voter
/**
* Delete User by ID
*
* #Rest\Delete("/{id}", name="delete_user")
* #Security("is_granted('remove', user)")
*
* #ApiDoc(
* section="5. Users",
* resource=true,
* description="Delete User",
* headers={
* {
* "name"="Authorization: Bearer [ACCESS_TOKEN]",
* "description"="Authorization key",
* "required"=true
* }
* },
* requirements={
* {
* "name"="id",
* "dataType"="string",
* "requirement"="\[a-z\-]+",
* "description"="Id of the object to receive"
* }
* },
* output="Status"
* )
*
* #param User $user
* #return Response;
*/
public function deleteAction(User $user)
{
//$this->denyAccessUnlessGranted('remove', $user);
$em = $this->getDoctrine()->getManager();
$em->remove($user);
$em->flush();
$view = $this->view('Success deleted', Response::HTTP_NO_CONTENT);
return $this->handleView($view);
}
But, if I use functions in the body $this->denyAccessUnlessGranted('remove', $user);, it's all right. Help me to understand...
Settings
services:
user.user_voter:
class: OD\UserBundle\Security\UserVoter
arguments: ['#security.access.decision_manager']
public: false
tags:
- { name: security.voter }
Voter
namespace OD\UserBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use OD\UserBundle\Entity\User;
class UserVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
const REMOVE = 'remove';
protected function supports($attribute, $subject)
{
# if the attribute isn't one we support, return false
if (!in_array($attribute, array(self::VIEW, self::EDIT, self::REMOVE))) {
return false;
}
# only vote on Booking objects inside this voter
if (!$subject instanceof User) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
# the user must be logged in; if not, deny access
return false;
}
switch ($attribute) {
case self::VIEW:
return $this->canView($subject, $user);
case self::EDIT:
return $this->canEdit($subject, $user);
case self::REMOVE:
return $this->canRemove($subject, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canView(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canEdit(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canRemove(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
}
* #Security("is_granted('remove', removingUser)")
* #param User $removingUser
* #return Response;
*/
public function deleteAction(User $removingUser)
This code works well. This code works well. I did not find the confirmation, but it seems the user is reserved for the current user

Symfony Two edit actions (activate/desactivate) one is working and the other doesn't work

I have two actions that edit the entity 'user' attribute 'etat' : activateAction makes the 'etat' equals to 1 if it was equal to 0, else it returns a flashbag message 'the account is already activated', and the desactivateAction is supposed to do the opposite, but it doesn't work!!! Here is the code of both activate and desactivate actions:
/**
* #Route("/admin/gestEtat/act/{iduser}", name="act")
*
* #Template()
*/
public function activateAction($iduser)
{
$user=new user();
$em=$this->getDoctrine()->getManager();
$repository = $em->getRepository("CNAMCMSBundle:user");
$user = $repository->find($iduser);
if($user)
{
if ($user->getEtat()==1) {
$this->get("session")->getFlashBag()->add('act',"Ce compte est déjà activé!");
return $this->redirectToRoute('gestEtat',
array());
}
elseif ($user->getEtat()==0) {
$user->setEtat('1');
$em->merge($user);
$em->flush();
return $this->redirectToRoute('gestEtat',
array());
}
}
}
/**
* #Route("/admin/gestEtat/desact/{id}",name="desact")
*
* #Template()
*/
public function desactivateAction($id)
{
$user=new user();
$em=$this->getDoctrine()->getManager();
$repository = $em->getRepository("CNAMCMSBundle:user");
$user = $repository->find($id);
//$session = new Session();
//$session->start();
//$users=$session->get('users_table');
if($user)
{
if ($user->getEtat()==0) {
$this->get("session")->getFlashBag()->add('desact',"Ce compte est déjà désactivé!");
// return $this->render('CNAMCMSBundle:Default:gestEtat.html.twig',
return $this->redirectToRoute('gestEtat',
array());
}
elseif ($user->getEtat()==1) {
$user->setEtat('0');
$em->merge($user);
$em->flush();
// return $this->render('CNAMCMSBundle:Default:gestEtat.html.twig',
return $this->redirectToRoute('gestEtat',
array());
}
}
}
Seems like you're performing setEtat('0') by passing in the string '0'. If the entity variable is a boolean, you should send it as a (true/false) or (1/0). If it is a string, you should be checking in your code elseif (getEtat()=='1')
The way it stands, checking if (getEtat()==1) will be the same as if (getEtat()), which will return true if getEtat() is not explicitly a false/null boolean, or a null variable.

Finding out what changed via postUpdate listener in Symfony 2.1

I have a postUpdate listener and I'd like to know what the values were prior to the update and what the values for the DB entry were after the update. Is there a way to do this in Symfony 2.1? I've looked at what's stored in getUnitOfWork() but it's empty since the update has already taken place.
You can use this ansfer Symfony2 - Doctrine - no changeset in post update
/**
* #param LifecycleEventArgs $args
*/
public function postUpdate(LifecycleEventArgs $args)
{
$changeArray = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($args->getObject());
}
Found the solution here. What I needed was actually part of preUpdate(). I needed to call getEntityChangeSet() on the LifecycleEventArgs.
My code:
public function preUpdate(Event\LifecycleEventArgs $eventArgs)
{
$changeArray = $eventArgs->getEntityChangeSet();
//do stuff with the change array
}
Your Entitiy:
/**
* Order
*
* #ORM\Table(name="order")
* #ORM\Entity()
* #ORM\EntityListeners(
* {"\EventListeners\OrderListener"}
* )
*/
class Order
{
...
Your listener:
class OrderListener
{
protected $needsFlush = false;
protected $fields = false;
public function preUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
return $this->setFields($entity, $eventArgs);
}
public function postUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
foreach ($this->fields as $field => $detail) {
echo $field. ' was ' . $detail[0]
. ' and is now ' . $detail[1];
//this is where you would save something
}
$eventArgs->getEntityManager()->flush();
return true;
}
public function setFields($entity, LifecycleEventArgs $eventArgs)
{
$this->fields = array_diff_key(
$eventArgs->getEntityChangeSet(),
[ 'modified'=>0 ]
);
return true;
}
public function isCorrectObject($entity)
{
return $entity instanceof Order;
}
}
You can find example in doctrine documentation.
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}

Combining #Gedmo\NestedTree and #ORM\UniqueEntity

I'm creating a folder structure implemented with the NestedTree behaviour.
Furthermore, I don't want that two folders may have the same name if they are siblings.
For this, I use the combination of #UniqueEntity and #UniqueConstraint annotations, but it does not work.
First my entity (stripped to the minimum since it is 100% identical to the NestedTree defaults) :
/**
* #ORM\Entity
* #Gedmo\Tree(type="nested")
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
* #UniqueEntity(fields={"parent", "name"})
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="uniq_url", columns={"parent_id", "name"})})
*/
class Folder
{
/**
* #ORM\Column(type="string", nullable=false)
*/
protected $name;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Folder", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $parent;
}
First try (ignoreNull = true)
When I create two folders with the same name, I have an integrity constraint violation, meaning that the #UniqueConstraints in the database worked but that the #UniqueEntity didn't :
Integrity constraint violation: 1062 Duplicate entry 'name_of_folder' for key 'uniq_url'
Second try (ignoreNull = false)
I also tried with the ignoreNull key set to false (the default is true) :
#UniqueEntity(fields={"parent", "name"}, ignoreNull=false)
but then I get this error :
Warning: ReflectionProperty::getValue() expects parameter 1 to be object, null given in vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php line 670
I've nailed the error down to these lines in Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator :
$criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity);
if ($constraint->ignoreNull && null === $criteria[$fieldName]) {
return;
}
if ($class->hasAssociation($fieldName)) {
/* Ensure the Proxy is initialized before using reflection to
* read its identifiers. This is necessary because the wrapped
* getter methods in the Proxy are being bypassed.
*/
$em->initializeObject($criteria[$fieldName]);
$relatedClass = $em->getClassMetadata($class->getAssociationTargetClass($fieldName));
//problem
$relatedId = $relatedClass->getIdentifierValues($criteria[$fieldName]);
if (count($relatedId) > 1) {
throw new ConstraintDefinitionException(
"Associated entities are not allowed to have more than one identifier field to be " .
"part of a unique constraint in: " . $class->getName() . "#" . $fieldName
);
}
$criteria[$fieldName] = array_pop($relatedId);
}
The problem appears on the line marked with //problem. It appears that $criteria[$fieldName] === null is the reason of the error.
So here I am, not knowing what to do...
Does anybody have an idea on what's going on ?
Thank you.
There is no easy way to get out of this situation.
I finally went my own way and created a validator :
Entity
/**
* #ORM\Entity(repositoryClass="Ibiz\DoctrineExtensionsBundle\Entity\Repository\NestedTreeRepository")
* #Gedmo\Tree(type="nested")
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="uniq_url", columns={"parent_id", "name"})})
* #IbizAssert\UniquePath("getName")
*/
class Folder
{
/**
* #ORM\Column(type="string", nullable=false)
*/
protected $name;
public function getName()
{
return $this->name;
}
}
Validator/Constraints/UniquePath.php
namespace Ibiz\DoctrineExtensionsBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UniquePath extends Constraint
{
public $em = null;
public $errorMethod = null;
public $message = 'The name "%name%" already exists.';
public $service = 'ibiz.validator.unique_path';
public function validatedBy()
{
return $this->service;
}
public function getRequiredOptions()
{
return array('errorMethod');
}
public function getDefaultOption()
{
return 'errorMethod';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
Validator/Constraints/UniquePathValidator.php
namespace Ibiz\DoctrineExtensionsBundle\Validator\Constraints;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class UniquePathValidator extends ConstraintValidator
{
private $registry;
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
public function validate($entity, Constraint $constraint)
{
if ($constraint->errorMethod === null)
{
throw new ConstraintDefinitionException('ErrorMethod should be set');
} else if (!is_string($constraint->errorMethod)) {
throw new UnexpectedTypeException($constraint->errorMethod, 'string');
}
if ($constraint->em) {
$em = $this->registry->getManager($constraint->em);
} else {
$em = $this->registry->getManagerForClass(get_class($entity));
}
$className = $this->context->getClassName();
$repo = $em->getRepository($className);
$count = $repo->getSameNameSiblingsCount($entity);
if ($count != 0) {
$this->context->addViolation($constraint->message, array('%name%' => $entity->{$constraint->errorMethod}()));
}
}
}
Entity/Repository/NestedTreeRepository.php
namespace Ibiz\DoctrineExtensionsBundle\Entity\Repository;
use Gedmo\Tree\Entity\Repository\NestedTreeRepository as BaseRepository;
class NestedTreeRepository extends BaseRepository
{
public function getSameNameSiblingsCountQueryBuilder($node)
{
$meta = $this->getClassMetadata();
if (!$node instanceof $meta->name) {
throw new InvalidArgumentException("Node is not related to this repository");
}
$config = $this->listener->getConfiguration($this->_em, $meta->name);
$qb = $this->_em->createQueryBuilder();
$qb->select($qb->expr()->count('n.id'))
->from($config['useObjectClass'], 'n');
if ($node->getParent() === null) {
$qb->where($qb->expr()->andx(
$qb->expr()->eq('n.name', ':name'),
$qb->expr()->isNull('n.parent')
))
->setParameters(array(
'name' => $node->getName(),
));
} else {
$qb->leftJoin('n.parent', 'p')
->where($qb->expr()->andx(
$qb->expr()->eq('n.name', ':name'),
$qb->expr()->eq('p.name', ':parent')
))
->setParameters(array(
'name' => $node->getName(),
'parent' => $node->getParent()->getName(),
));
}
return $qb;
}
public function getSameNameSiblingsCountQuery($node)
{
return $this->getSameNameSiblingsCountQueryBuilder($node)->getQuery();
}
public function getSameNameSiblingsCount($node)
{
return $this->getSameNameSiblingsCountQuery($node)->getSingleScalarResult();
}
}

Resources