Symfony 5 - find(ELEMENT) doesn't work (?) in delete function (CRUD) - php

I try to create my first API in Symfony. I have a little problem with my Delete function.
My entity class:
<?php
namespace App\Entity;
use App\Repository\InFlowsRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=InFlowsRepository::class)
*/
class InFlows
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
public int $id;
/**
* #ORM\Column(type="string", length=255)
*/
public string $inFlowName;
/**
* #ORM\Column(type="float")
*/
public float $inFlowValue;
/**
* #ORM\Column(type="string")
*/
public String $inFlowsDate;
public function getId(): ?int
{
return $this->id;
}
public function getInFlowName(): ?string
{
return $this->inFlowName;
}
public function setInFlowName(string $inFlowName): self
{
$this->inFlowName = $inFlowName;
return $this;
}
public function getInFlowValue(): ?float
{
return $this->inFlowValue;
}
public function setInFlowValue(float $inFlowValue): self
{
$this->inFlowValue = $inFlowValue;
return $this;
}
public function getInFlowsDate(): ?String
{
return $this->inFlowsDate;
}
public function setInFlowsDate(String $inFlowsDate): self
{
$this->inFlowsDate = $inFlowsDate;
return $this;
}
}
And my Delete controller:
/**
* #Route("inflows/delete/", name="delete_inflow")
* #throws Exception
*/
public function inFlowDelete(Request $id): JsonResponse {
try {
$repo = $this->getDoctrine()->getManager();
$inflows = $repo->getRepository(InFlows::class)->find($id);
if (!$inflows) {
throw new \JsonException("There is no data to delete!");
}
} catch (Exception $e) {
return new JsonResponse(["data"=>$e->getMessage()]);
}
$repo->remove($inflows);
$repo->flush();
return new JsonResponse("Success!");
}
When I run my script I get an error:
An exception occurred while executing \u0027SELECT t0.id AS id_1, t0.in_flow_name AS in_flow_name_2, t0.in_flow_value AS in_flow_value_3, t0.in_flows_date AS in_flows_date_4 FROM in_flows t0 WHERE t0.id = ?\u0027 with params [{\u0022attributes\u0022:{},\u0022request\u0022:{},\u0022query\u0022:{},\u0022server\u0022:{},\u0022files\u0022:{},\u0022cookies\u0022:{},\u0022headers\u0022:{}}]:\n\nSQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for type integer: \u0022DELETE \/inflows\/delete\/?id=1 HTTP\/1.1\r\nAccept: *\/*\r\nAccept-Encoding: gzip, deflate, br\r\nCache-Control: no-cache\r\nConnection: keep-alive\r\nContent-Length: \r\nContent-Type: \r\nHost: 127.0.0.1:8000\r\nMod-Rewrite: On\r\nPostman-Token: 6f77209a-8bad-4109-93a8-4c43647d7849\r\nUser-Agent: PostmanRuntime\/7.28.0\r\nX-Php-Ob-Level: 1\r\n\r\n\u0022e2
I don't have idea why my instruction "where t0.id = ?" looks like.
Why my "find($id)" function doesn't work?
Is the way to fix it out?
Thanks for response.

/**
* #Route("inflows/delete/{id}", name="delete_inflow")
*/
public function inFlowDelete(InFlows $inFlows): JsonResponse {
$em = $this->getDoctrine()->getManager();
$em->remove($inFlows);
$em->flush();
return new JsonResponse("Success!");
}

I think I know what's the problem, here your input is a Request (httpd request ) which contains a lot of informationu can check this in your browser , anyway so the best solution is to pass the id as an argument and delee it like this :
/**
* #Route("inflows/delete/{id}", name="delete_inflow")
* #throws Exception
*/
public function inFlowDelete(int $id): JsonResponse {
try {
$repo = $this->getDoctrine()->getManager();
$inflows = $repo->getRepository(InFlows::class)->find($id);
if (!$inflows) {
throw new \JsonException("There is no data to delete!");
}
} catch (Exception $e) {
return new JsonResponse(["data"=>$e->getMessage()]);
}
$repo->remove($inflows);
$repo->flush();
return new JsonResponse("Success!");
}
try it and keep me updated !

I think that the problem is that u didn't use persist , when u act on the data base u have to persist your changement so it'll look like
/**
* #Route("inflows/delete/", name="delete_inflow")
* #throws Exception
*/
public function inFlowDelete(Request $id): JsonResponse {
try {
$repo = $this->getDoctrine()->getManager();
$inflows = $repo->getRepository(InFlows::class)->find($id);
if (!$inflows) {
throw new \JsonException("There is no data to delete!");
}
} catch (Exception $e) {
return new JsonResponse(["data"=>$e->getMessage()]);
}
$repo->remove($inflows);
$repo->persist();
$repo->flush();
return new JsonResponse("Success!");
}
if i can give you an advice dont use remove() because it'll remove physically your row , somtimes it's better to remove logically , so use setDeletedAt(new \Datetime());

So I delete my table and create new migration files. My code was still the same which I posted in #nikoshr solution. And It works ! Very strange but as they say - darkest under the lantern.

Related

DQL request in SYmfony having two relations and returning an empty array

Hello there have something i dont understand about SQL i guess. I linked below the code and the image about my db shema for help you to understand.
So my problem with this request is that it return an empty array and i dont understand why its not working as from my understanding it should return an array containing all the "r.messages" that have the specified ID related to the fields relation. What do i do wrong ?
PS: for some context helping your understanding, im trying to build a messagery system from a user to another. The SQL request i try to do should return the conversation between two users.
public function listOfMessages($value, $value2)
{
return $this->createQueryBuilder('user')
->select('r.message')
->innerJoin('user.sender', 's')
->innerJoin('user.receiver', 'r')
->where('s.sender = :value')
->andWhere('r.recipient = :value2')
->setParameter(':value', $value)
->setParameter(':value2', $value2)
->getQuery()
->getResult()
;
}
DB shema
Entity User
use App\Repository\PrivateMessageRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=PrivateMessageRepository::class)
*/
class PrivateMessage
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $message;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="sender")
*/
private $sender;
/**
* #ORM\ManyToOne(targetEntity=user::class, inversedBy="receiver")
*/
private $recipient;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $isRead = 0;
/**
* #ORM\Column(type="datetime_immutable", nullable=true)
*/
private $sentAt;
public function getId(): ?int
{
return $this->id;
}
public function getMessage(): ?string
{
return $this->message;
}
public function setMessage(?string $message): self
{
$this->message = $message;
return $this;
}
public function getSender(): ?User
{
return $this->sender;
}
public function setSender(?User $sender): self
{
$this->sender = $sender;
return $this;
}
public function getRecipient(): ?user
{
return $this->recipient;
}
public function setRecipient(?user $recipient): self
{
$this->recipient = $recipient;
return $this;
}
public function getIsRead(): ?bool
{
return $this->isRead;
}
public function setIsRead(?bool $isRead): self
{
$this->isRead = $isRead;
return $this;
}
public function getSentAt(): ?\DateTimeImmutable
{
return $this->sentAt;
}
public function setSentAt(?\DateTimeImmutable $sentAt): self
{
$this->sentAt = $sentAt;
return $this;
}
}
Entity privateMessage
use App\Repository\PrivateMessageRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=PrivateMessageRepository::class)
*/
class PrivateMessage
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $message;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="sender")
*/
private $sender;
/**
* #ORM\ManyToOne(targetEntity=user::class, inversedBy="receiver")
*/
private $recipient;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $isRead = 0;
/**
* #ORM\Column(type="datetime_immutable", nullable=true)
*/
private $sentAt;
public function getId(): ?int
{
return $this->id;
}
public function getMessage(): ?string
{
return $this->message;
}
public function setMessage(?string $message): self
{
$this->message = $message;
return $this;
}
public function getSender(): ?User
{
return $this->sender;
}
public function setSender(?User $sender): self
{
$this->sender = $sender;
return $this;
}
public function getRecipient(): ?user
{
return $this->recipient;
}
public function setRecipient(?user $recipient): self
{
$this->recipient = $recipient;
return $this;
}
public function getIsRead(): ?bool
{
return $this->isRead;
}
public function setIsRead(?bool $isRead): self
{
$this->isRead = $isRead;
return $this;
}
public function getSentAt(): ?\DateTimeImmutable
{
return $this->sentAt;
}
public function setSentAt(?\DateTimeImmutable $sentAt): self
{
$this->sentAt = $sentAt;
return $this;
}
}
Could you please use the web debug toolbar to extract the readable query and fire it agains your sql server.
I think your repository dql is wrong - hope i figure it out of the head correct.
You have to go over the message repository, not over the user repo.
You have to select the messages for user a and user b visaverce
$qb = $this->createQueryBuilder('m');
$qb->where(
$qb->expr()->orX(
$qb->expr()->andX(
$qb->expr()->eq('m.sender', ':sender'),
$qb->expr()->eq('m.recipient', ':recipient')
),
$qb->expr()->andX(
$qb->expr()->eq('m.sender', ':recipient'),
$qb->expr()->eq('m.recipient', ':sender')
)
)
)
->setParameter(':sender', $sender)
->setParameter(':recipient', $recipient)
->getQuery()
->getResult()
You say you want the conversation between the two users. Architectural i think, there is a missing table named conversation. Think about that:
A user can have a conversation to one-or-many users
A conversation can have one-or-many message(s)
A message can have a sender and a recipient
Maybe a better solution if you want to have more than one conversation ...
Thank you for your time and tips, i tried your dql request and it return "NULL".
About doing it into the message repo instead of user repo i did it like this because in my entity user i have two methods that can retrieve messages sent and received.
But anyways even trying it inside message repo return an empty array.
I tryed with rawsql and got some good result:
public function stack($id)
{
$rawSql =
"SELECT pm.sent_at as date, pm.message, user.login as login FROM private_message as pm
INNER JOIN user on user.id = pm.sender_id
WHERE pm.sender_id = $id
UNION
SELECT pm.sent_at as date, pm.message, user.login as login FROM private_message as pm
INNER JOIN user on user.id = pm.recipient_id
WHERE pm.recipient_id = $id
ORDER BY date DESC"
;
$conn = $this->getEntityManager()->getConnection()->prepare($rawSql);
$stmt = $conn->executeQuery([$rawSql]);
return $stmt->fetchAll();
}
However this request have some weird behavior specially when i set two different $id variable if i manually set the user id and recipient id it start to duplicate some messages and also the user.login returned is the same for all messages sent and recieved. But still a kind of progress as i have the conversation with this sql request but its not a good solution for long term as the ->fetchAll() method is depreciated and going to be removed from doctrine api in 2023.
By the way the clause UNION in my sql request seem to not have its equal version with DQL do you have any tips to make a UNION with a DQL request ?
I think you are right about adding a conversation table ill have to think back about my db architecture maybe it will make the process more simple and intuitive

Passing POST json object for ArrayCollection data by ajax in Symfony

I want to insert Order data in my Symfony 5 application. OrderDetails ArrayCollection data of an Order entity class. Order and OrderDetails ArrayCollection data get by JSON object ajax post. How to passing POST json object for ArrayCollection data by ajax in Symfony.
Entity Code:
class Order
{
public const NUM_ITEMS = 10;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $orderNo;
/**
* #ORM\Column(type="datetime")
*/
private $orderDate;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=OrderDetail::class, mappedBy="orders")
*/
private $orderDetails;
public function __construct()
{
$this->orderDetails = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getOrderNo(): ?string
{
return $this->orderNo;
}
public function setOrderNo(string $orderNo): self
{
$this->orderNo = $orderNo;
return $this;
}
public function getOrderDate(): ?\DateTimeInterface
{
return $this->orderDate;
}
public function setOrderDate(\DateTimeInterface $orderDate): self
{
$this->orderDate = $orderDate;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|OrderDetail[]
*/
public function getOrderDetails(): Collection
{
return $this->orderDetails;
}
public function addOrderDetail(OrderDetail $orderDetail): self
{
if (!$this->orderDetails->contains($orderDetail)) {
$this->orderDetails[] = $orderDetail;
$orderDetail->setOrders($this);
}
return $this;
}
public function removeOrderDetail(OrderDetail $orderDetail): self
{
if ($this->orderDetails->contains($orderDetail)) {
$this->orderDetails->removeElement($orderDetail);
// set the owning side to null (unless already changed)
if ($orderDetail->getOrders() === $this) {
$orderDetail->setOrders(null);
}
}
return $this;
}
}
JS File Code:
// Creating Order Json Object
var orderObj = { "orderNo":"", "orderDate":"", "name":"" };
orderObj.orderNo = $("#text_name").val();
orderObj.orderDate = $("#text_mobileno").val();
orderObj.name = $("#text_email").val();
// Set 2: Ajax Post
// Here i have used ajax post for saving/updating information
$.ajax({
type: 'POST',
contentType: 'application/json;',
url:'/cart/ordersave',
data: JSON.stringify(orderObj),
dataType: 'json',
success: function (response)
{
// alert(response['data']);
//alert(1);
},
error:function(){
alert('ajax failed');
}
});
Controller Code:
/**
* #Route("/cart/ordersave", name="cart_order_save", methods={"POST"})
*
*/
public function ordersave(Request $request, SessionInterface $session)
{
if ($request->isXMLHttpRequest()) {
$content = $request->getContent();
if (!empty($content)) {
$params = json_decode($content, true);
$order = new Order();
$order->setOrderNo('ON-101/20');
$order->setOrderDate(new \DateTime());
$order->setName($params['name']);
$order->setMobileNo($params['mobileno']);
$order->setEmail($params['email']);
$order->setDeliveryAddress($params['address']);
$order->setCity($params['city']);
$order->setState($params['state']);
$order->setZipcode($params['zipcode']);
$order->setPaymentBy(1);
$order->setDeliveryDate(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
$lastId = $order->getId();
$session->set('lastOrderIDSession', $lastId);
}
$this->addFlash('notice', 'Order created successfully!');
return new JsonResponse(array('data' => $lastId));
// return new JsonResponse(array('data' => $params));
}
return new Response('Error!', 400);
}
How to get ArrayCollection data in the controller and insert its database table.
I'd advise you using a symfony form, with a form collection, and it will work by itself. You seem to want to use ajax, and even with forms, you can submit the form in javascript without reloading the page.
This will help : https://symfony.com/doc/current/form/form_collections.html
If you really don't want to do that, well you totally can submit an array with the order details data, then iterate on it, create an OrderDetail entity for each, persist them, etc...

How to display a message in the default locale when the message is not available in the current locale ? Prezent Translations

I'm working on a multilingual website, using the Prezent bundle to translate my entities.
Actually, the input in all the locales works, but I have some issues to display messages when they are not defined in the current locale.
Here is an extract of my Category entity (the field "name" is translated) :
/**
* #ORM\Entity(repositoryClass="App\Repository\CategoryRepository")
*/
class Category extends TranslatableEntity
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="CategoryTranslation", mappedBy="translatable", cascade={"persist", "remove"}, indexBy="locale")
*/
protected $translations;
public function __construct()
{
$this->translations = new ArrayCollection();
$this->translationEntity = 'CategoryTranslation';
}
public function getId(){
return $this->id;
}
public function setId($id){
$this->id = $id;
}
public function getName()
{
return $this->translate()->getName();
}
public function setName($name){
$this->translate()->setName($name);
return $this;
}
}
The translate method is in TranslatableEntity, here is the code :
abstract class TranslatableEntity extends AbstractTranslatable
{
/**
* #Prezent\CurrentLocale
*/
protected $currentLocale;
protected $translationEntity;
/**
* Cache current translation. Useful in Doctrine 2.4+
*/
protected $currentTranslation;
public function getCurrentLocale()
{
return $this->currentLocale;
}
public function setCurrentLocale($locale)
{
$this->currentLocale = $locale;
return $this;
}
/**
* Translation helper method
*/
public function translate($locale = null)
{
if (null === $locale) {
$locale = $this->currentLocale;
}
if (!$locale) {
throw new \RuntimeException('No locale has been set and currentLocale is empty');
}
if ($this->currentTranslation && $this->currentTranslation->getLocale() === $locale) {
return $this->currentTranslation;
}
if (!$translation = $this->translations->get($locale)) {
$className=$this->translationEntity;
$translation = new $className;
$translation->setLocale($locale);
$this->addTranslation($translation);
}
$this->currentTranslation = $translation;
return $translation;
}
}
I use this way to display the translated names in Twig :
{{ cat.translations.get(app.request.locale).name }}
This works but I'm pretty sure that it is not the right way to do it. Moreover, the method throws an error when I try to display a name which not defined in the current locale.
I think that ...
{{ cat.translations.get(app.request.locale).name is defined ? cat.translations.get(app.request.locale).name : cat.translations.get(default_locale).name }}
.. would solve but I'm also pretty sure that the case of "not available for this locale" is supported by the bundle.
Do you have any idea of what I am doing wrong ?

Symfony4 setter where getter match id route?

I'm kindly new on Symfony
I'm doing a vote system but i guess this should work for like,
At the moment my controller function is this, this only create a new row with 1vote, but not update any $id created before.
/**
* #Route("/public/{id}/vote", name="poll_vote", methods="GET|POST")
*/
public function vote(Request $request, Poll $poll): Response
{
$inc = 1;
$em = $this->getDoctrine()->getManager();
$entity = new Poll();
$entity->setVotes($inc++);
$em->persist($entity);
$em->flush();
}
return $this->redirectToRoute('poll_public');
}
This is my button from twig template
<a href="{{ path('poll_vote', {'id': poll.id}) }}">
An this is my entity
class Poll
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $votes;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getVotes(): ?int
{
return $this->votes;
}
public function setVotes(?int $votes): self
{
$this->votes = $votes;
return $this;
}
}
I have no idea about how can match my getID from my entity and match for the $id from the #Route.
Any guide or suggestion would be really appreciate.
Thanks
EDIT:
Updated with the correct function after Arne answer:
/**
* #Route("/public/{id}", name="poll_vote", methods="GET|POST")
*/
public function vote($id)
{
$entityManager = $this->getDoctrine()->getManager();
$poll = $entityManager->getRepository(Poll::class)->find($id);
if (!$poll) {
throw $this->createNotFoundException(
'No polls found for id '.$id
);
}
$poll->setVotes($poll->getVotes()+1);
$entityManager->flush();
return $this->redirectToRoute('poll_public', [
'id' => $poll->getId()
]);
}
basically you have to get the ID from your request, query the Entitty Repository for your Poll Entity, update the votes and persist it back to your database.
Get the ID from your request
$id = $request->query->get('id');
Query the repository:
$entityManager = $this->getDoctrine()->getManager();
$poll= $entityManager->getRepository(Poll::class)->find($id);
Update the votes:
$poll->setVotes($poll->getVotes()+1);
Persist to the DB:
$entityManager->persist($poll);
$entityManager->flush();
Alternatively you could also use the ParamConverter to let Symfony get the Poll object for you. More information about updating objects can be found in the Doctrine Guide.
Note that yor route will only match existing polls, since id is a required parameter in the URL. You might add another route without an ID which is being used for creating new Poll entities.

Symfony 3.4 - The EntityManager is closed

I have an application run with Symfony 3.4 with MySql and I get a error: 'The EntityManager is closed'. My application running two ways:
1 - A Console application thats is called by a sh script every time. This console app make many inserts in a database table.
2 - A HTTP Route that also insert in same table.
When de console app is running in background if i call the http route i get the error 'The EntityManager is closed'. If I stop de backgroud app the http route works. It's as if the two apps console and http use the same instance EntityManager.
My code:
I Create a service called AbstractRepositoryService. All of my services that manage repositories should extend.
<?php
abstract class AbstractRepositoryService
{
/**
*
* #var EntityManagerIntergace - $em
*/
protected $em;
/**
*
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
/**
*
*
* #param String
*
* #return #mixed
*
* #throws RuntimeException
*/
public function __call($method, $args) {
$repository = $this->em->getRepository(static::ENTITY);
if (!method_exists($repository, $method)) {
throw new RuntimeException(
sprintf("Method '%s' not found.", $method),
500
);
}
try {
return call_user_func_array(array($repository, $method), $args);
} catch(Exception $e) {
throw new Exception($e->getMessage(), 500);
}
}
}
My UserRepositoryService where the exception is thrown in the flush method
<?php
final class UserRepositoryService extends AbstractRepositoryService
{
/**
*
* #const String
*/
const ENTITY = 'AppBundle\\Entity\\User';
/**
*
* #param User
*/
public function insert(User $user) {
try {
$this->em->persist($user);
$this->em->flush($user);
} catch (Exception $e) {
throw new Exception($e->getMessage(), 500);
}
}
}
And finaly my service declaration:
app.services.user_repository_service:
public: true
class: AppBundle\Services\UserRepositoryService
arguments:
- '#doctrine.orm.entity_manager'
Solved!
I created a method that generate new EntityManager before insert and works now.
protected function createNewEntityManager() {
return $this->em->create(
$this->em->getConnection(),
$this->em->getConfiguration(),
$this->em->getEventManager()
);
}
And in insert:
public function insert(Crawler $crawler) {
try {
$this->createNewEntityManager();
$this->em->persist($crawler);
$this->em->flush($crawler);
$this->em->close();
} catch (Exception $e) {
throw new Exception($e->getMessage(), 500);
}
}

Categories