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);
}
}
Related
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.
I'm currently migrating a ZF2 application to ZF3.
Mostly everything is going smoothly but I'm stuck on one thing.
In my Module.php, I have an ACL management using zend-permissions-acl.
class Module
{
protected $defaultLang = 'fr';
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
if (!$e->getRequest() instanceof ConsoleRequest){
$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError'));
$eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'));
$eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
$this->initAcl($e);
$eventManager->attach('route', array($this, 'checkAcl'));
}
}
public function checkAcl(MvcEvent $e) {
$app = $e->getApplication();
$sm = $app->getServiceManager();
$route = $e -> getRouteMatch() -> getMatchedRouteName();
$authService = $sm->get('AuthenticationService');
$jwtService = $sm->get('JwtService');
$translator = $sm->get('translator');
$identity = null;
try {
$identity = $jwtService->getIdentity($e->getRequest());
} catch(\Firebase\JWT\ExpiredException $exception) {
$response = $e->getResponse();
$response->setStatusCode(401);
return $response;
}
if(is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation
$authService->setJwtMode(false);
$identity = $authService->getIdentity();
}
$userRole = 'default';
$translator->setLocale($this->defaultLang);
if(!is_null($identity))
{
$userRole = $identity->getType();
//check if client or prospect
if($userRole >= User::TYPE_CLIENT)
{
$userManagementRight = UserRight::CREATE_USERS;
if($identity->hasRight($userManagementRight))
$userRole = 'userManagement';
}
$translator->setLocale($identity->getLang());
}
if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) {
$response = $e -> getResponse();
$response->setStatusCode(403);
return $response;
}
public function initAcl(MvcEvent $e) {
//here is list of routes allowed
}
}
My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
Basically, I just need to inject dependencies into Module.php.
I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.
Any feedback on this would be greatly appreciated.
Regards,
Robert
To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)
You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )
In your module.config.php:
'listeners' => [
// Listing class here will automatically have them "activated" as listeners
ActiveSessionListener::class,
],
'service_manager' => [
'factories' => [
// The class (might need a) Factory
ActiveSessionListener::class => ActiveSessionListenerFactory::class,
],
],
The Factory
<?php
namespace User\Factory\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use User\Listener\ActiveSessionListener;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
class ActiveSessionListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $entityManager */
$entityManager = $container->get(EntityManager::class);
/** #var AuthenticationService $authenticationService */
$authenticationService = $container->get(AuthenticationService::class);
return new ActiveSessionListener($authenticationService, $entityManager);
}
}
The Listener
<?php
namespace User\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\AuthenticationService;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Mvc\MvcEvent;
/**
* Class ActiveSessionListener
*
* #package User\Listener
*
* Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager.
* A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it.
*/
class ActiveSessionListener implements ListenerAggregateInterface
{
/**
* #var AuthenticationService
*/
protected $authenticationService;
/**
* #var ObjectManager|EntityManager
*/
protected $objectManager;
/**
* #var array
*/
protected $listeners = [];
/**
* CreatedByUserListener constructor.
*
* #param AuthenticationService $authenticationService
* #param ObjectManager $objectManager
*/
public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager)
{
$this->setAuthenticationService($authenticationService);
$this->setObjectManager($objectManager);
}
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000);
}
/**
* #param Event $event
*
* #throws \Doctrine\Common\Persistence\Mapping\MappingException
* #throws \Doctrine\ORM\ORMException
*/
public function haveDoctrineManagerUser(Event $event)
{
if ($this->getAuthenticationService()->hasIdentity()) {
// Get current unmanaged (by Doctrine) session User
$identity = $this->getAuthenticationService()->getIdentity();
// Merge back into a managed state
$this->getObjectManager()->merge($identity);
$this->getObjectManager()->clear();
// Get the now managed Entity & replace the unmanaged session User by the managed User
$this->getAuthenticationService()->getStorage()->write(
$this->getObjectManager()->find(User::class, $identity->getId())
);
}
}
/**
* #return AuthenticationService
*/
public function getAuthenticationService() : AuthenticationService
{
return $this->authenticationService;
}
/**
* #param AuthenticationService $authenticationService
*
* #return ActiveSessionListener
*/
public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener
{
$this->authenticationService = $authenticationService;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*
* #return ActiveSessionListener
*/
public function setObjectManager($objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
}
The important bits:
The Listener class must implement ListenerAggregateInterface
Must be activated in the listeners key of the module configuration
That's it really. You then have the basic building blocks for a Listener.
Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.
NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']);
}
public function callbackFunction (MvcEvent $event) {...}
src/Controller/DataTableController.php
<?php
use DataTables\DataTablesInterface;
/**
* Symfony 3.4 and above
*
* #Route("/users", name="users")
*
* #param Request $request
* #param DataTablesInterface $datatables
* #return JsonResponse
*/
public function usersAction(Request $request, DataTablesInterface $datatables): JsonResponse
{
try {
// Tell the DataTables service to process the request,
// specifying ID of the required handler.
$results = $datatables->handle($request, 'users');
return $this->json($results);
}
catch (HttpException $e) {
// In fact the line below returns 400 HTTP status code.
// The message contains the error description.
return $this->json($e->getMessage(), $e->getStatusCode());
}
}
/**
* Symfony 3.3 and below
*
* #Route("/users", name="users")
*
* #param Request $request
* #return JsonResponse
*/
public function usersAction(Request $request): JsonResponse
{
try {
/** #var \DataTables\DataTablesInterface $datatables */
$datatables = $this->get('datatables');
// Tell the DataTables service to process the request,
// specifying ID of the required handler.
$results = $datatables->handle($request, 'users');
return $this->json($results);
}
catch (HttpException $e) {
// In fact the line below returns 400 HTTP status code.
// The message contains the error description.
return $this->json($e->getMessage(), $e->getStatusCode());
}
}
I get the error message:
The structure for a class, (not related to Symfony) is
class MyClassController() {
public function myMethodAction() {
}
}
There is not way to avoid a parse error because this is just not valid syntax of 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();
The Problem:
While running a Daemon service that uses Doctrine from the Factory classes below there is a memory issue. When the Daemon Service starts it runs about 175MB. A day later it's about 250MB, one more day and it's at 400MB. I'm looking as to what is causing the increase in memory and how I could bring it down.
Things I've tried:
$em->clear(); // This kinda helps
$em->close(); // this causes issues
$em->getConnection()->getConfiguration()->setSQLLogger(null);
--env=prod should take care of setSQLLogger(null), correct?
Is there anything I should be doing to help with memory issues using Doctrine 2.x and Symfony 2.1.x?
Created a factory to handle connections
===================== START EMFactory =====================
<?php
namespace NS\Bundle\EMBundle;
use Doctrine\ORM\EntityManager;
class EMFactory
{
/**
* #var
*/
private $container;
/**
* #param $container
*/
public function __construct($container)
{
$this->container = $container;
}
/**
* #return EntityManager
*/
public function getBlahEntityManager()
{
return $this->getContainer()->get('doctrine.orm.blah_manager_entity_manager');
}
/**
* #return EntityManager
*/
public function getFooEntityManager()
{
return $this->getContainer()->get('doctrine.orm.foo_manager_entity_manager');
}
/**
* #return EntityManager
*/
public function getBarEntityManager()
{
return $this->getContainer()->get('doctrine.orm.bar_manager_entity_manager');
}
/**
* #return mixed
*/
public function getContainer()
{
return $this->container;
}
/**
* #param $container
* #return $this
*/
public function setContainer($container)
{
$this->container = $container;
return $this;
}
public function closeEntityManager(EntityManager $em)
{
try {
$em->clear(); // This kinda helps
//$em->close(); // this causes issues
//$em->getConnection()->getConfiguration()->setSQLLogger(null); // --env=prod should take care of this
} catch (\Exception $e) {
// exception here
}
}
}
===================== END EMFactory =====================
I use an Abstract Class that constructs the EMFactory
===================== Start Abstract Class =====================
/**
* #param \Symfony\Component\DependencyInjection\Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
$this->entityManagerFactory = new EMFactory($container);
}
===================== END Abstract Class =====================
Here is an example of how I'm using the EM, The class extends the Abstract class above
===================== START Working Example #1 =====================
// calling like this looks to be working as expected
$fooEM = $this->getEntityManagerFactory()->getFooEntityManager();
$barResults = $fooEM->getRepository('NS\Bundle\EMBundle\Entity\Bar')->findOneBy(array('id' => 1));
if (!is_object($barResults)) {
throw new \Exception("Bar is a non object.");
}
// some logic here ...
$this->getEntityManagerFactory()->closeEntityManager($fooEM);
===================== END Working Example #1 =====================
Here is another example of how I'm using the EM, The class extends the Abstract class above
===================== START Working Example #2 =====================
// calling from functions like this
$fooEM = $this->getEntityManagerFactory()->getFooEntityManager();
$dql = 'SELECT b.*
FROM NS\Bundle\EMBundle\Entity\Bar b
WHERE b.id = :id';
$query = $fooEM->createQuery($dql);
$query->setParameter('id', 1);
$barResults = $query->getResult();
$this->getEntityManagerFactory()->closeEntityManager($fooEM);
return $barResults;
===================== END Working Example #2 =====================
Here is another example of how I'm using the EM, The class extends the Abstract class above
===================== START Working Example #3 =====================
// calling from functions like this
$fooEM = $this->getEntityManagerFactory()->getFooEntityManager();
$barEntity = new Bar();
$barEntity->setId(1);
$barEntity->setComment('this is foo-ie');
$fooEM->persist($barEntity);
$fooEM->flush();
$this->getEntityManagerFactory()->closeEntityManager($fooEM);
unset($barEntity);
===================== END Working Example #3 =====================
These are just some basic examples but it's just the queries that get more complex.
Does anything stand out that say, Optimize me?
Your issue might come from your instanciations of your entity managers. If you have a specific set of them, you might rather use Symfony2 Dependency Injection instead of calling the container.
Each time you use your accessors, you'll instantiate a new Entity Manager, hence consume more memory (and as it's a daemon, you never truly release it). By using DI, you'll always have the same instance.
Your EMFFactory should then look like this:
<?php
namespace NS\Bundle\EMBundle;
use Doctrine\ORM\EntityManager;
class EMFactory
{
/**
* #var
*/
private $fooEm;
/**
* #var
*/
private $barEm;
/**
* #var
*/
private $blahEm;
/**
* #param $fooEm
* #param $barEm
* #param $blahEm
*/
public function __construct($fooEm, $barEm, $blahEm)
{
$this->fooEm = $fooEm;
$this->barEm = $barEm;
$this->blahEm = $blahEm;
}
/**
* #return EntityManager
*/
public function getBlahEntityManager()
{
return $this->blahEm;
}
/**
* #return EntityManager
*/
public function getFooEntityManager()
{
return $this->fooEm;
}
/**
* #return EntityManager
*/
public function getBarEntityManager()
{
return $this->barEm;
}
/**
* #return mixed
*/
public function getContainer()
{
return $this->container;
}
/**
* #param $container
* #return $this
*/
public function setContainer($container)
{
$this->container = $container;
return $this;
}
public function closeEntityManager(EntityManager $em)
{
try {
$em->clear(); // This kinda helps
//$em->close(); // this causes issues
//$em->getConnection()->getConfiguration()->setSQLLogger(null); // --env=prod should take care of this
} catch (\Exception $e) {
// exception here
}
}
}
Then, tweak your service definitions to give the various EMs to your config, and define your EMFactory as a service as well.
This solved the connection issue we were having.
Needed to close just the connection
public function closeEntityManager(EntityManager $em)
{
try {
$em->clear(); // This kinda helps
$em->getConnection()->close(); // this seems to work
} catch (\Exception $e) {
// exception here
}
}