How to pass doctrine entity manager to some class constructor?
For example, i set-up and create entity manager $em in file _someAction.php.
In the same file i instantiate the class, and pass entity manager to the constructor, but em does not work in the class. I am getting
Fatal error: Uncaught exception 'Doctrine\ORM\ORMException' with message 'The EntityManager is closed.'
class dealErrCl
use Doctrine\ORM\EntityManager;
use someNamespace\LogErr;
class dealErrCl {
private $em; //entity manager to save error to the entity
public function __construct( \Doctrine\ORM\EntityManager $em ) {
$this->em = $em;
}
public function flush($errArr) {
$this->le = new LogErr();
if( isset($errArr['title']) ) {
$this->le->setTitle($errArr['title']);
}
/*...*/
$this->em->persist($this->le);
$this->em->flush();
}
_someAction.php
include DbCon.php; // configures doctrine and creates $em. If i use this em in this file, it works, but if i tryt to pass it to some class constructor, it gives Fatal error.
use LogBundle\Components\ErrC\dealErrCl;
$dealErrIn = new dealErrCl($em);
If i use this $em in _someAction.php file, it works, but if i tryt to pass it to some dealErrCl constructor, it gives Fatal error.
It means that there is an error during the flush, which closes the entity manager.
There maybe many possible errors:
1) setting null on non-null field
2) using reserved keywords for field name
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words
POSTGRE reserved words :
https://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html
MYSQL reserved words:
https://dev.mysql.com/doc/refman/5.7/en/keywords.html
Thus better always enclose all flush, inside a try-catch block.
Related
This question already has an answer here:
Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests
(1 answer)
Closed 1 year ago.
I have some code that persist an object with doctrine, then use its newly generated id to link it to another object.
But when I want to test the code, the id is not generated
class Foo extends DefaultDocument
{
/**
* #var int
* #MongoDB\Field(type="int")
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id
}
class Link
{
/**
* #var int
* #MongoDB\Field(type="int")
*/
private $fooId;
}
class Service
{
public function doesSomething()
{
$foo = new Foo();
$this->documentManager->persist($foo);
$link = new Link();
$link->setFooId($foo->getId());
}
}
class TestService
{
private function createService()
{
return new Service();
}
public function testDoesSomething()
{
$service = $this->createService();
$service->doesSomething();
}
}
When launching the test I get the following error :
TypeError: Argument 1 passed to ...\Link::setFooId() must be of the
type int, null given
Is this a default logic of my code, or the normal behavior of doctrine ?
EDIT :
Finally found the right topic for my question :
Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests
I used his first solution
Because it is unit test, If you want to get id, you need flush entity, and after that doctrine fill the ID field, but it will be functional test, because you have to use database.
In unit test you dont able to test ID assignment, it is possible only in functional test
I'm working on a bundle and i need to load a doctrine resolve_target_entities from a configuration parameter.
This article should be my solution, the fact is that using the bundle it seems not to load the "compiler pass class".
This is my bundle class
class PersonalBundle extends Bundle
{
public function build(ContainerBuilder $container){
parent::build($container);
$container->addCompilerPass(new ResolveTargetEntitiesPass());
}
}
This is the ResolveTargetEntitiesPass class
class ResolveTargetEntitiesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
// Gets the custom entity defined by the user (or the default one)
$customEntityClass = $container->getParameter('personal.custom_class');
// Skip the resolve_target_entities part if user has not defined a different entity
if (DefaultClassInterface::DEFAULT_ENTITY_CLASS == $customEntityClass) {
return;
}
// Throws exception if the class isn't found
if (!class_exists($customEntityClass)) {
throw new ClassNotFoundException(sprintf("Can't find class %s ", $customEntityClass));
}
// Get the doctrine ResolveTargetEntityListener
$def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
// Adds the resolve_target_enitity parameter
$def->addMethodCall('addResolveTargetEntity', array(
DefaultClassInterface::DEFAULT_ENTITY_CLASS, $customEntityClass, array()
));
// This was added due this problem
// https://stackoverflow.com/a/46656413/7070573
if (version_compare(Version::VERSION, '2.5.0-DEV') < 0 && !$def->hasTag('doctrine.event_listener')) {
$def->addTag('doctrine.event_listener', array('event' => 'loadClassMetadata'));
} elseif (!$def->hasTag('doctrine.event_subscriber')) {
$def->addTag('doctrine.event_subscriber');
}
}
}
When i use the class it raises this error
Expected value of type "PersonalBundle\Entity\DefaultClass"
for association field "PersonalBundle\Entity\Group#$defaultClass", got
"App\Entity\CustomClass" instead.
As i said it seems not to load the ResolveTargetEntitiesPass...
Thanks
So i solved the problem changing the priority of the compiler pass.
I've tried to move the bundle on top in config/bundle.php and it started working, then following this https://symfony.com/blog/new-in-symfony-3-2-compiler-passes-improvements i've left the default type but increased the priority (from 0, default, to 1).
I'm not sure which service has been "downgraded" so if anyone has an idea it's welcome.
<?php
// ...
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
class PersonalBundle extends Bundle
{
public function build(ContainerBuilder $container){
parent::build($container);
$container->addCompilerPass(new ResolveTargetEntitiesPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
}
}
I am trying to embed collection of Tag forms to Service form, according to this tutorial. Tag and Service entities have many-to-many relationship.
Form is rendering correctly. But when I submit form, I get
Could not determine access type for property "tagList"
error. I don't understand why new Tag object is not added to the Service class by calling the addTag() method.
ServiceType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'label' => 'Title'
))
;
$builder->add('tagList', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)));
}
Service class
{
....
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="serviceList",cascade={"persist"})
*/
private $tagList;
/**
* #return ArrayCollection
*/
public function getTagList()
{
return $this->tagList;
}
/**
* #param Tag $tag
* #return Service
*/
public function addTag(Tag $tag)
{
if ($this->tagList->contains($tag) == false) {
$this->tagList->add($tag);
$tag->addService($this);
}
}
/**
* #param Tag $tag
* #return Service
*/
public function removeTag(Tag $tag)
{
if ($this->tagList->contains($tag)) {
$this->tagList->removeElement($tag);
$tag->removeService($this);
}
return $this;
}
}
Tag class
{
/**
* #ORM\ManyToMany(targetEntity="Service", inversedBy="tagList")
* #ORM\JoinTable(name="tags_services")
*/
private $serviceList;
/**
* #param Service $service
* #return Tag
*/
public function addService(Service $service)
{
if ($this->serviceList->contains($service) == false) {
$this->serviceList->add($service);
$service->addTag($this);
}
return $this;
}
/**
* #param Service $service
* #return Tag
*/
public function removeService(Service $service)
{
if ($this->serviceList->contains($service)) {
$this->serviceList->removeElement($service);
$service->removeTag($this);
}
return $this;
}
}
ServiceController
public function newAction(Request $request)
{
$service = new Service();
$form = $this->createForm('AppBundle\Form\ServiceType', $service);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($service);
$em->flush();
return $this->redirectToRoute('service_show', array('id' => $service->getId()));
}
return $this->render('AppBundle:Service:new.html.twig', array(
'service' => $service,
'form' => $form->createView(),
));
}
Could you please try to implement code from this URL?
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#owning-and-inverse-side-on-a-manytomany-association
First, please try to change mapped/inverse sides, and remove $service->addTag($this); from Tag::addService method.
Short version:
I just ran into this problem and solved it by adding a setter for the affected property:
Could not determine access type for property "tagList"
public function setTagList(Array $tagList)
{
$this->tagList = $tagList;
}
Long version:
The error message is signaling that Symfony is trying to modify the object's state, but cannot figure out how to actually make the change due to the way its class is set up.
Taking a look at Symfony's internals, we can see that Symfony gives you 5 chances to give it access and picks the best one in this order from top to bottom:
A setter method named setProperty() with one argument:
This is the first thing Symfony checks for and is the most explicit way to achieve this. As far as I'm aware this is the best practice:
class Entity {
protected $tagList;
//...
public function getTagList()
{
return $this->tagList;
}
//...
}
A combined getter and setter in one method with one argument:
It's important to realize that this method will also be accessed by Symfony in order to get the object's state. Since those method calls don't include an argument, the argument in this method must be optional.
class Entity {
protected $tagList;
//...
public function tagList($tags = null)
{
if($reps){
$this->tagList = $tags;
} else {
return $this->tagList;
}
}
//...
}
The affected property being declared as public:
class Entity {
public $tagList;
//... other properties here
}
A __set magic method:
This will affect all properties rather than just the one you intended.
class Entity {
public $tagList;
//...
public function __set($name, $value){
$this->$name = $value;
}
//...
}
A __call magic method (in some cases):
I wasn't able to confirm this, but the internal code suggests this is possible when magic is enabled on PropertyAccessor's construction.
Only using one of the above strategies is required.
Maybe the problem is that Symfony can't access that property?
If you look at where that exception is thrown (writeProperty method in the PropertyAccessor class) it says it can be thrown:
If the property does not exist or is not public.
In the tutorial you mentioned it has property $tags, and method addTag. I'm just guessing here, but maybe there's a convention where it tries to call a method names add($singularForm) and this is failing for you because the property is tagList and the method is addTag.
I'm not 100% sure, but you could try debugging by setting a stop point in that Symfony method to see why it's being thrown.
Maybe you forgot in the __construct() of Service class and Tag class to initialize $tagList and $serviceList like this ?
$this->tagList = new ArrayCollection();
$this->serviceList = new ArrayCollection();
This seems like an error with your constructor. Try this :
public function __construct()
{
$this-> tagList = new \Doctrine\Common\Collections\ArrayCollection();
}
It's a long shot, but looking at your annotations I think the problem might be related to your manyToMany relationship. Try to change the owning side and inverse side (Swap the relationship) unless you specifically need to update from both ends (In that case I think the only solution is to add the objects manually or use oneToMany relationships).
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view)
This is a problem related to Doctrine I have suffered before, see:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html
Based on Symfony 3.3.10
I actually faced this problem many and many times, finally once i discovered where this problem was coming from, depending on the name you give to your entity property it can happen that the adder and the remover for your collection property aren't exactly what you are expecting.
Example: Your entity properity name is "foo" and you would expect the adder to be called "addFoo" and remover "removeFoo", but then all of a sudden the "Could not determine access type for property" appear.
So you start going into fear searching for w/e problems in your code, instead you just have to look this file inside Symfony core files:
vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Inside this file there's a method called findAdderAndRemover.
Go there with your debugger and you will eventually find out that symfony searches for weird name for your adder/remover, they may actually end with "um" or "on" or "us" depending on the language (human language) you used to name them. Since i'm Italian this happen quite often.
Watch out for that, since the fix may be as simple as changing the name used for your add/remove method inside your entity to make them match with what Symfony core is looking for.
This happens to me when i use bin/console doctrine:generate:entities to create the methods automatically for me
If you are using symfony, and use EntityRepository instead of CollectionType, make sure you use the 'multiple' => true, on your form build, otherwise the input will be for one entity and not for many, therefore it will call the setTagList instead of using the methods addTagList and removeTagList.
I had integrated doctrine 2 in codeigniter 2. i had the database which i convert into entity in store in models/Entity
my controller goes like this ..
public function __construct() {
parent::__construct();
$this->em = $this->doctrine->em;
$this->load->model('doctrine_model');
}
public function index()
{
$this->doctrine_model->get_object();
}
}
and model goes like this....
Class Doctrine_model extends CI_Controller
{
function __construct() {
parent::__construct();
//$this->load->library('doctrine');
//$accountTable = Doctrine_Core::getTable('ss_class');
$this->em = $this->doctrine->em;
}
function get_object()
{
$records = $this->em->getRepository("Entity\SsClass")->findAll();
}
}
when i run this code i get this error
Fatal error: Cannot redeclare class SsClass in D:\xampp\htdocs\new_doctrine\application\models\Entity\SsClass.php on line 14
As i had already loaded the doctrine library in autoload but cannot figure out what is going on here...
getRepository method load your entity automatically so probably your entity class is autoload in other place such as config.php or require function.
I'm learning doctrine2, and having a problem how to call constructor automatically.
For example, in my entity I have
/**
* #Entity
*/
class User{
....
public function __construct() {
exit('in');
}
}
and when I get the object this way:
$userObj = $em->find('User', 1);
I do get that object from database, but constructor is never called.
I want to put some common things in constructor, like validation rules, or even to put sample code from the doctrine documentation like
$this->comments = new ArrayCollection();
This ofcourse works when I create new object in code for creating a user like
$user = new User(); //now constructor works just fine
Now, what is the "proper" way of getting the entity? I doubt I have to call constructor manually each time I user $em->find() with $user0bj->__construct(); ? This would kinda sucks then... Or I should use something other then ->find() to get single entity properly?
I know I can user #PrePersist, and I am using it to actually do validation checks etc.
I guess that I'm probably missing something here, or I'm trying to use constructor in a poor way. Thanks for any explanations and guides!
I'm pretty certain that find or similar isn't expected to call the constructor...
You need to hook into the #PostLoad event.
Why would you want to call the constuctor of already persisted entity? When you need to validate it you should have done the validation or initializations before you have persisted it. So When you call a already persisted entity there is no point to validate it.
The right place to put validation and other initializations is the constructor method of entity.
Eg.
/**
* #Entity
*/
class User{
protected $name;
public function __construct($name) {
if (isset($name)) {
//** validate the name here */
$this->name=$name;
} else {
throw new Exception("no user name set!");
}
}
}
According to the doctrine2 documentation Doctrine2 never calls __construct() method of entities.
http://www.doctrine-project.org/docs/orm/2.0/en/reference/architecture.html?highlight=construct
doctrine uses reflection to instantiate your object without invoking your constructor.
Since PHP 5.4 , you can use reflection to instanciate a class without
calling the constructor using
ReflectionClass::newInstanceWithoutConstructor
the instantiator of doctrine use it like :
private function buildFactory(string $className) : callable
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return [$reflectionClass, 'newInstanceWithoutConstructor'];
}
$serializedString = sprintf(
'%s:%d:"%s":0:{}',
is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
strlen($className),
$className
);
$this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
return static function () use ($serializedString) {
return unserialize($serializedString);
};
}
Doctrine ORM will "rewrite" your class, it generate a new class that implement \Doctrine\ORM\Proxy\Proxy
And it rewrite the construct method:
/**
* #param \Closure $initializer
* #param \Closure $cloner
*/
public function __construct($initializer = null, $cloner = null)
{
$this->__initializer__ = $initializer;
$this->__cloner__ = $cloner;
}
You can see it inside the cache folder ${CACHE}/doctrine/orm/Proxies.
You will need both #ORM\HasLifecycleCallbacks on the class + #ORM\PostLoad on a specific function of your choice.
Beware! If you put it on the constructor it will override loaded database data!
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="dossier")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Dossier
{
// ...
/**
* The normal constructor stays as usual
*/
public function __construct()
{
$this->takenActions = new ArrayCollection();
$this->classifications = new ArrayCollection();
$this->dossierProblems = new ArrayCollection();
$this->internalNotes = new ArrayCollection();
}
/**
* Triggers after the entity has been loaded in the EntityManager (e.g. Doctrine's ->find() etc...)
* The constructor does not get called. Some variables still need a default value
* Must be in combination with "ORM\HasLifecycleCallbacks" on the class
*
* #ORM\PostLoad
*/
public function postLoadCallback(): void
{
// Only put a default value when it has none yet
if (!$this->dossierProblems)
$this->dossierProblems = new ArrayCollection();
if (!$this->internalNotes)
$this->internalNotes = new ArrayCollection();
}
// ...
}