I am still new to Symfony and can't figure out this error.
Basically I try to remove the byUser from my database on the table wish. When the flush() function is executed I get the error # unable to fetch the response from the backend: unexpected EOF
My controller:
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/wish/delete/{id}', name: 'delete_wish')]
public function deleteWish(Wish $wish): Response
{
$user = $this->getUser();
$user->removeWish($wish);
$this->entityManager->persist($wish);
$this->entityManager->flush();
$this->addFlash("success", "Successfully removed the wish!");
return $this->redirectToRoute('wishlist');
}
From user.php:
public function removeWish(Wish $wish): self
{
if ($this->wishes->removeElement($wish)) {
// set the owning side to null (unless already changed)
if ($wish->getByUser() === $this) {
$wish->setByUser(null);
}
}
return $this;
}
Dev server logs:
[2021-05-07T15:16:45.680666+00:00] doctrine.DEBUG: "START TRANSACTION" [] []
[2021-05-07T15:16:45.684106+00:00] doctrine.DEBUG: UPDATE wish SET by_user_id = ? WHERE id = ? [null,3] []
My code worked before and I did not change anything on purpose. So I am quite lost where to start looking. Would anyone have some idea?
Try edit your user entity atribute wish like this: The main is onDelete="SET NULL"
class User
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Wish")
* #ORM\JoinColumn(name="wish_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $wish;
}
Then you can delete
$this->entityManager->remove($wish);
$this->entityManager->flush();
I dont know how looks removeWish($wish); but you can try:
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/wish/delete/{id}', name: 'delete_wish')]
public function deleteWish(Wish $wish): Response
{
$user = $this->getUser();
$this->entityManager->remove($wish);
$this->entityManager->flush();
$this->addFlash("success", "Successfully removed the wish!");
return $this->redirectToRoute('wishlist');
}
Related
I'm working on a Symfony 6 project and I'm using sqlite as db.
I have a ManyToOne relation between two entities: Neighborhood and PropertyForSale.
When I delete a Neighborhood I want the $Neighborhood field of PropertyForSale to be set to null so I added:
#[ORM\JoinColumn(onDelete: 'SET NULL')] to the property:
#[ORM\ManyToOne(inversedBy: 'propertiesForSale')]
#[ORM\JoinColumn(onDelete: 'SET NULL')]
private ?Neighborhood $neighborhood = null;
Everything seems to work properly if I change the database to MySql but with Sqlite this attribute seems to be ignored. I know has something to do with the default foreign key behavior in sqlite and
PRAGMA foreign_keys = ON; should be executed but I canĀ“t find I way to make it work with Symfony and Doctrine; Any ideas?
I share a bigger portion of my code:
// PropertyForSale.php
#[ORM\Entity(repositoryClass: PropertyForSaleRepository::class)]
class PropertyForSale
{
// ...
#[ORM\ManyToOne(inversedBy: 'propertiesForSale')]
#[ORM\JoinColumn(onDelete: 'SET NULL')]
private ?Neighborhood $neighborhood = null;
// ...
public function getNeighborhood(): ?Neighborhood
{
return $this->neighborhood;
}
public function setNeighborhood(?Neighborhood $neighborhood): self
{
$this->neighborhood = $neighborhood;
return $this;
}
}
// Neighborhood.php
#[ORM\Entity(repositoryClass: NeighborhoodRepository::class)]
class Neighborhood
{
// ...
#[ORM\OneToMany(mappedBy: 'neighborhood', targetEntity: PropertyForSale::class)]
private Collection $propertiesForSale;
// ...
public function getPropertiesForSale(): Collection
{
return $this->propertiesForSale;
}
public function addPropertiesForSale(PropertyForSale $propertiesForSale): self
{
if (!$this->propertiesForSale->contains($propertiesForSale)) {
$this->propertiesForSale->add($propertiesForSale);
$propertiesForSale->setNeighborhood($this);
}
return $this;
}
public function removePropertiesForSale(PropertyForSale $propertiesForSale): self
{
if ($this->propertiesForSale->removeElement($propertiesForSale)) {
// set the owning side to null (unless already changed)
if ($propertiesForSale->getNeighborhood() === $this) {
$propertiesForSale->setNeighborhood(null);
}
}
return $this;
}
}
The only workaround I found was to add an event listener on the entity preRemove event and manually set to null the relation:
// NeighborhoodListener
namespace App\EventListener;
use Doctrine\ORM\EntityManagerInterface;
class NeighborhoodListener
{
public function __construct(private EntityManagerInterface $entityManager) {}
public function preRemove($args) {
$properties = $args->getObject()->getPropertiesForSale();
foreach ($properties as $property) {
$property->setNeighborhood(null);
$this->entityManager->persist($property);
}
$this->entityManager->flush();
}
}
I'm coding an API in Symfony with API Platform and I have an issue when I persist a relation of my object.
I have few entities. Entity Lead can have few LeadJob and for each LeadJob I create a Project. I use a Subscriber to trigger those creations.
Project relation of entity LeadJob :
/**
* #ORM\OneToOne(targetEntity=Project::class, inversedBy="leadJob", cascade={"persist", "remove"})
*/
private $project;
public function getProject(): ?Project
{
return $this->project;
}
public function setProject(?Project $project): self
{
$this->project = $project;
return $this;
}
LeadJob relation of entity Project :
/**
* #ORM\OneToOne(targetEntity=LeadJob::class, mappedBy="project", cascade={"persist", "remove"})
*/
private $leadJob;
public function getLeadJob(): ?LeadJob
{
return $this->leadJob;
}
public function setLeadJob(?LeadJob $leadJob): self
{
$this->leadJob = $leadJob;
// set (or unset) the owning side of the relation if necessary
$newProject = null === $leadJob ? null : $this;
if ($leadJob->getProject() !== $newProject) {
$leadJob->setProject($newProject);
}
return $this;
}
And the Subscriber that create the Project :
final class LeadCreateProjectSubscriber implements EventSubscriber
{
protected EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function getSubscribedEvents(): array
{
return [
Events::postPersist,
Events::postUpdate,
];
}
public function postPersist(LifecycleEventArgs $event): void
{
$entity = $event->getObject();
if (!$entity instanceof Lead) {
return;
}
$this->createProject($entity);
}
public function postUpdate(LifecycleEventArgs $event): void
{
$entity = $event->getObject();
if (!$entity instanceof Lead) {
return;
}
$this->createProject($entity);
}
private function createProject(Lead $lead): void
{
if (LeadStatusEnum::FINISHED !== $lead->getStatus()) {
return;
}
foreach ($lead->getLeadJobs() as $leadJob) {
$project = (new Project())
->setOwner($leadJob->getUser())
->addUser($lead->getOwner())
->setOrganization($lead->getOrganization())
->setContact($lead->getContact())
->setName('Lead '.$lead->getSource()->getName())
->setJob($leadJob->getJob())
->setLeadJob($leadJob) //this line that causes the error
->setComment($lead->getDescription());
$this->entityManager->persist($project);
}
$this->entityManager->flush();
}
}
So, when I trigger the creation of an Project with everything I need, I have this error message thrown from my Subscriber. There is some properties that I didn't notice, this is the raw error message :
"An exception occurred while executing 'INSERT INTO lead_job (id, deleted_at, created_at,
updated_at, job_id, user_id, lead_id, project_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
with params [\"eafb3b13-bc14-4eb8-92e8-cf3acc55719e\", \"2021-07-22
16:54:45\"]:\n\nSQLSTATE[08P01]: <<Unknown error>>: 7 ERROR: bind message supplies 2 parameters, but prepared statement \"\" requires 8"
The only way that work is to persist the project, flush, set the relation and persist it in the Subscriber. And delete the setLeadJob on the Project object :
$this->entityManager->persist($project);
$this->entityManager->flush();
$leadJob->setProject($project);
$this->entityManager->persist($leadJob);
$this->entityManager->flush();
Why the cascade persist is not doing the job? And why I have this error?
From the Doctrine documentation:
postUpdate, postRemove, postPersist
The three post events are called
inside EntityManager#flush(). Changes in here are not relevant to the
persistence in the database, but you can use these events to alter
non-persistable items, like non-mapped fields, logging or even
associated classes that are not directly mapped by Doctrine.
I think this is not very clear, so in other words: don't persist (other) entities inside a postPersist listener. Because postPersist happens during the flush event, new persisted entities (like your Project) aren't flushed. And flushing during a flush event leads to unexpected behaviour, like the error in your question.
You should use a onFlush event instead:
class FlushExampleListener
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Lead) {
$this->createProject($entity);
}
}
}
private function createProject(Lead $lead): void
{
// your logic here
}
}
For work I need to read through Symfony 5: The Fast Track. But in chapter 9 I have some problems. I have two entities Conference and Comment. The Comment entity is connected with the ID of the Conference.
Comment:
/**
* #ORM\ManyToOne(targetEntity=Conference::class, inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $conference;
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getConference(): ?Conference
{
return $this->conference;
}
My EasyAdminController looks like this:
public function configureMenuItems(): iterable
{
return [
MenuItem::linkToCrud('Conference', '', Conference::class),
MenuItem::linkToCrud('Comment', '', Comment::class)>setController(CommentCrudController::class),
];
}
And the CommentCrudController likt that:
public function configureFields(string $pageName): iterable
{
return [
TextField::new('author'),
TextEditorField::new('text'),
TextField::new('email'),
DateTimeField::new('createdAt'),
IdField::new('conference')->formatValue(function ($value) {
dd($value);
}),
TextField::new('photoFilename'),
];
}
So first my problem is that I need values as a object of Conference. Because I don't know how to do it right I tried to use formatValue but the dd($value)is never executed. Can someone help me with that problem or link me somewhere where I can find the answer.
Thank you in advantage.
I am trying to monitor what happens in my app. For this I created this class:
class ChangeLogListener implements EventSubscriber
{
private $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function getSubscribedEvents()
{
return array(
'postPersist',
'postUpdate',
'onDelete',
);
}
public function postPersist(LifecycleEventArgs $args)
{
# Avoid to log the logging process
if (!$args->getEntity() instanceof ChangeLog)
$this->createLog($args, 'creation');
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->createLog($args, 'update');
}
public function preRemove(LifecycleEventArgs $args)
{
# Handle the log creation
$this->createLog($args, 'remove');
}
public function createLog(LifecycleEventArgs $args, $action)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$entity = $args->getEntity();
$user = $this->tokenStorage->getToken()->getUser();
$changes[] = $uow->getEntityChangeSet($entity);
$cl = new ChangeLog();
$cl->setDate(new \DateTime());
$cl->setUser($user);
$cl->setEntityName(get_class($entity));
$cl->setEntityId($entity->getId());
$cl->setAction($action);
$cl->setChangeset($changes);//<---change this
$cl->setDescription('');
$em->persist($cl);
$em->flush();
}
}
But I have some problems with changes, I don't know how I can correctly write them into the DB and make them readable. Maybe there are some methods to do this correctly?
When I try to flush changes, I always get error:
String data, right truncated: 1406 Data too long for column
'change_set' at row 1"
My ChangeLog.orm.yml:
#...
fields:
changeSet:
type: array
#...
Do a var_dump($changes);die(); after your getEntityChangeSet() line and figure out what you actually want from that data set. It is currently too long for the field you are trying to save it in. It will need to be reduced.
Things we want to achieve in our application are:
Non-unique usernames [Done]
Unique username and email combination
FosUserBundle will fetch all users (on user login) with given username and checks if any of the users has the given password (hashed with bcrypt). When a user is found it logs the user in.
Making username non unique was quite simple by just overriding the username field in the user ORM. But we're kinda stuck with how to proceed in achieving the last two points. We've started creating a custom User Provider but it seems Symfony Security can only handle one user(name).
Is there anyone with experience that might be able to help us? If you need more information or code snippets, please ask. Thank you in advance!
So after looking through alot of the documentation for the Symfony Security module we figured it out.
We added an extra field (displayname) to the User model because Symfony is completely build around the fact that usernames are Unique. It always fetches the first user with the given username, this is not what we wanted.
So we started with writing our own Guard Authentication System, this was pretty straight forward although we had to make some adjustments.
This was all working well, but we ran into a problem with the built-in UsernamePasswordFormAuthenticationListener, this listener was still picking up the displayname from the login form. We actually want the unique username so that Symfony knows which user to use.
We created a custom listener that extended the standard listener and made sure the username was not fetched from the login form but from the user token.
So our flow is now like this: The user fills in his username (actually his displayname) and password, the system fetches all users with that displayname. Then we loop these users and check if someone has that password. If so, authenticate the user.
On user create the admin fills in the displayname and the system will autoincrement this as a username. (admin_1, admin_2, ...).
We have to monitor if what #kero said is true, but with Bcrypt it seems that even with simple passwords like "123", it results in a different hash for each user.
The only thing that is left is to have a UniqueConstraint on the unique combination of the displayname and email. If anyone knows how this can be achieved in our orm.xml and form, thank you.
http://symfony.com/doc/current/security/guard_authentication.html
Custom Guard Authenticator
class Authenticator extends AbstractGuardAuthenticator
{
private $encoderFactory;
private $userRepository;
private $tokenStorage;
private $router;
public function __construct(EncoderFactoryInterface $encoderFactory, UserRepositoryInterface $userRepository, TokenStorageInterface $tokenStorage, Router $router)
{
$this->encoderFactory = $encoderFactory;
$this->userRepository = $userRepository;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
}
/**
* Called on every request. Return whatever credentials you want,
* or null to stop authentication.
*/
public function getCredentials(Request $request)
{
$encoder = $this->encoderFactory->getEncoder(new User());
$displayname = $request->request->get('_username');
$password = $request->request->get('_password');
$users = $this->userRepository->findByDisplayname($displayname);
if ($users !== []) {
foreach ($users as $user) {
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
return ['username' => $user->getUsername(), 'password' => $user->getPassword()];
}
}
} else {
if ($this->tokenStorage->getToken() !== null) {
$user = $this->tokenStorage->getToken()->getUser();
return ['username' => $user->getUsername(), 'password' => $user->getPassword()];
}
}
return null;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if ($credentials !== null) {
return $userProvider->loadUserByUsername($credentials["username"]);
}
return null;
}
public function checkCredentials($credentials, UserInterface $user)
{
if ($user !== null) {
return true;
} else {
return false;
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$exclusions = ['/login'];
if (!in_array($request->getPathInfo(), $exclusions)) {
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
throw $exception;
}
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = array(
// you might translate this message
'message' => 'Authentication Required'
);
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
Custom listener
class CustomAuthListener extends UsernamePasswordFormAuthenticationListener
{
private $csrfTokenManager;
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null)
{
parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_token_id' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfTokenManager = $csrfTokenManager;
$this->tokenStorage = $tokenStorage;
}
/**
* {#inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if ($user = $this->tokenStorage->getToken() !== null) {
$user = $this->tokenStorage->getToken()->getUser();
$username = $user->getUsername();
if ($this->options['post_only']) {
$password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
} else {
$password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
}
if (strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey));
} else {
return null;
}
}
}
Listener service
<service id="security.authentication.listener.form" class="Your\Path\To\CustomAuthListener" parent="security.authentication.listener.abstract" abstract="true" />