Symfony, Doctrine update not working as expected - php

Hey All I have this wired error that I cannot overcome. I am using Symfony Framweork and Doctrine for my DB interaction. I am trying to develop a simple CRUD API to grasp some of the concepts.
The actual problem is when I try to update an item in my DB it only works if the ID of the item is inside the DB else I get this error:
Error: Call to a member function setTitle() on a non-object
Have a look at my Repository:
<?php
namespace BooksApi\BookBundle\Repositories;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\QueryException;
class UpdateBookRepository
{
/**
* #var EntityManager
*/
public $em;
/**
* #param EntityManager $entityManager
*/
public function __construct(
EntityManager $entityManager
){
$this->em = $entityManager;
}
public function updateBook($id, $update)
{
try {
$book = $this->em->getRepository('BooksApiBookBundle:BooksEntity')
->find($id);
$book->setTitle($update);
$this->em->flush();
} catch (\Exception $em) {
throw new QueryException('003', 502);
}
return $book;
}
}
And My factory:
<?php
namespace BooksApi\BookBundle\Repositories;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\QueryException;
class UpdateBookRepository
{
/**
* #var EntityManager
*/
public $em;
/**
* #param EntityManager $entityManager
*/
public function __construct(
EntityManager $entityManager
){
$this->em = $entityManager;
}
public function updateBook($id, $update)
{
$book = $this->em->getRepository('BooksApiBookBundle:BooksEntity')
->find($id);
if ($book)
{
try {
$book->setTitle($update);
$this->em->flush();
} catch (\Exception $em) {
throw new QueryException('003', 502);
}
return $book;
} else {
return false;
}
}
}
The factory handles the response true or false, So in event where I will try to update an Item thats ID is not in DB the factory should respond with false, 'Unable to Update Book' instead i get the above error, Any idea why guys..?

#Tomazi, you can avoid error by checking if your object exists before calling "setTitle" method.
public function updateBook($id, $update)
{
$book = $this->em->getRepository('BooksApiBookBundle:BooksEntity')->find($id);
if ($book) {
$book->setTitle($update);
$this->em->flush();
return $book;
}
return null;
}

Make sure that $book object is not empty before proceeding with the code. The error indicates that $book is null/empty.
Also, persist your object before using flush.
$this->em->persist($book);
$this->em->flush();

Related

Symfony 5 tests phpunit Doctrine problem update database row

I have the following problem with doctrine when testing a symfony 5 application. Instead of updating the rows in the database, new rows are created when the persist () method is called or when cascade = {"persist"} is defined in Entity. The above issue only occurs in a test environment. Everything works fine in the app itself.
sample test code (maximally simplified to show the problem)
class GetReadyArticlesTest extends FunctionalDBTest
{
protected function setUp():void
{
parent::setUp();
$this->addFixture(new ConfigurationFixtures());
$this->addFixture(new CopyWriterTextOrderFixtures());
$this->executeFixtures();
}
protected function tearDown(): void
{
parent::tearDown();
}
public function testProcessSaveArticles()
{
$textOrderRepository = $this->entityManager->getRepository(CopywriterTextOrder::class);
$textOrderEntity = $textOrderRepository->find(1);
$textOrderEntity->setCharacters(4500);
$this->entityManager->persist($textOrderEntity);
$this->entityManager->flush();
}
}
Abstract class FunctionalDBTest:
abstract class FunctionalDBTest extends FunctionalTest
{
/**
* #var EntityManagerInterface
*/
protected $entityManager;
/**
* #var ORMExecutor
*/
private $fixtureExecutor;
/**
* #var ContainerAwareLoader
*/
private $fixtureLoader;
protected function setUp(): void
{
parent::setUp();
if ($this->getContainer()->getParameter('kernel.environment') !== 'test') {
die('Wymagane środowisko testowe');
}
$this->entityManager = $this
->getContainer()
->get('doctrine')
->getManager();
$schemaTool = new SchemaTool($this->entityManager);
$schemaTool->updateSchema($this->entityManager->getMetadataFactory()->getAllMetadata());
}
protected function addFixture(FixtureInterface $fixture): void
{
$this->getFixtureLoader()->addFixture($fixture);
}
protected function executeFixtures(): void
{
$this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
}
private function getFixtureExecutor(): ORMExecutor
{
if (!$this->fixtureExecutor) {
$this->fixtureExecutor = new ORMExecutor($this->entityManager, new ORMPurger($this->entityManager));
}
return $this->fixtureExecutor;
}
private function getFixtureLoader(): ContainerAwareLoader
{
if (!$this->fixtureLoader) {
$this->fixtureLoader = new ContainerAwareLoader($this->getContainer());
}
return $this->fixtureLoader;
}
protected function tearDown(): void
{
(new SchemaTool($this->entityManager))->dropDatabase();
parent::tearDown();
$this->entityManager->close();
$this->entityManager = null;
}
}
Removing the persist () method in this example fixes the problem. But in case I want to save a new relation to the table, it also generates a new main object. the problem only occurs in tests.

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

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.

Update every doctrine query before it is sent to the database

We are running a huge platform that has a single database for multiple frontends. Now we are about to try to identify our slow queries and get an better idea of from what page our traffic comes from.
I had the idea to inject the page name as a comment in every sql query to be able to see it when looking at the database using SHOW FULL PROCESSLIST
At the end it should look like this: /*PAGE NAME*/ SHOW FULL PROCESSLIST
If I do this in sequel pro it seems that the comment gets listed then:
How can I update every doctrine query using a listener/subscriber to inject a custom comment?
Doctrine DBAL allows you to define your own Connection class.
doctrine:
dbal:
wrapper_class: App\DBAL\MyConnectionWrapper
You could implement a child class of Doctrine\DBAL\Connection and override executeQuery() according to your needs.
class MyConnectionWrapper extends Connection
{
public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
{
$sql = '/*PAGE NAME*/ '.$sql;
return parent::executeQuery($sql, $params, $types, $qcp);
}
}
Please check this Symfony documentation : Doctrine Event Listeners and Subscribers To understand the following code
Here is how i did it for each update, in order to update the update time:
<?php
namespace App\EventListener;
use App\Entity\AbstractEntity;
use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Exception;
use Stringable;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use function is_object;
class DatabaseActivitySubscriber implements EventSubscriber
{
/**
* #var TokenStorageInterface
*/
private TokenStorageInterface $tokenStorage;
/**
* #var null|User
*/
private ?User $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
$this->user = null;
}
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::prePersist,
Events::preUpdate,
];
}
/**
* Initiate the name of the user creating the object with "LastName FirstName (id)"
*
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function prePersist(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setCreateUser($this->getUser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setCreateDate();
}
}
/**
* #return string|Stringable|UserInterface|null|User
*/
private function getUser()
{
if ($this->user instanceof User){
return $this->user;
}
$token = $this->tokenStorage->getToken();
if (null === $token) {
return null;
}
if (!is_object($user = $token->getUser())) {
return null;
}
$this->user = $user;
return $this->user;
}
/**
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function preUpdate(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setUpdateUser($this->getuser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setUpdateDate();
}
}
}
And add in service.yaml:
App\EventListener\DatabaseActivitySubscriber:
tags:
- { name: 'doctrine.event_subscriber' }

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);
}
}

PHP Symfony3, listener on instantiating a new entity

I would like to trigger on an instantiation of a new entity.
Can someone say if it is possible, and how ?
Waiting for an issue, I bring this solution in getting a new entity in a service… but I find it not enough optimized.
Here is (simplified extract of) my code with an added event "onCreate" in listener :
This is running well, but I have to call a new entity, passing by a service. I want my new entity filled directly while just doing "$entity = new entity();"
abstract class serviceBaseEntity extends serviceBaseClass {
const ENTITY_CLASS = ''; // here class of entity
protected $classname;
protected $shortname;
protected $EntityManager;
protected $ObjectManager;
public function __construct(ContainerInterface $container, RequestStack $request_stack) {
parent::__construct($container, $request_stack);
$this->EntityManager = $this->container->get('doctrine.orm.entity_manager');
$this->ObjectManager = $this->container->get('doctrine')->getManager();
$this->addEventSubscriber(new serviceAnnotations($container, $request_stack));
$this->classname = $this->calledClassname::ENTITY_CLASS;
$this->shortname = (new ReflectionClass($this->classname))->getShortName();
return $this;
}
protected function addEventSubscriber(EventSubscriber $EventSubscriber) {
$this->EntityManager->getEventManager()->addEventSubscriber($EventSubscriber);
return $this;
}
public function getNew() {
$entity = new $this->classname;
$eventArgs = new LifecycleEventArgs($entity, $this->ObjectManager);
$this->EntityManager->getEventManager()->dispatchEvent(AnnotationsBase::onCreate, $eventArgs);
return $entity;
}
Maybe you could use a sort of factory method in your entity, with a private constructor. For example:
Class MyEntity {
private function __construct( ) {
your code
}
public static function createEntity(your params) {
do some stuff
$e=new MyEntity();
do some other stuff
return $e;
}
}

Categories