Bug #92967
closedmakeInstance returns "unclean" instances of objects if used repeatedly
0%
Description
We've been porting code to TYPO3 10 and were switching from objectManager->get() to makeInstance() when we noticed that using makeInstance() repeatedly to create a list of new Model instances, the returned instance is a reference to the object that was created before.
Testcase:
$user =$this->feUserRepository->findOneByXid($userData['extId']);
if(!isset($user)){
$user = GeneralUtility::makeInstance(\Vendor\Extension\Domain\Model\FeUser::class);
}
// do stuff
If we repeatedly call this code, on the second user that is being created, makeInstance() returns the object poluted with information from the first user that was created and later persisted. We had to clone the returned object to keep the object cache that makeInstances manages "clean".
Updated by Simon Gilli almost 4 years ago
That's normal behavior in PHP, make sure you initialize your classes properly. Best practice is to initialize the properties, initialization in the constructor could lead to other side effects when the constructor is not called e.g. during testing.
Here is an example from the core, it's not an entity but it's the same for every class: https://github.com/TYPO3/TYPO3.CMS/blob/6c7aff19e0883a6def7cf81ce53225b64fbf16cc/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php#L59
Updated by Johannes Rebhan almost 4 years ago
We've extended FeUser and have initialized all class attributes properly. And it certainly isn't normal behaviour in PHP that a new object has the same values as an object of the same type created before (If I create those model instances with new instead of makeInstance(), they naturally don't have the same spl_object_id, nor do they have properties poluted). I've also checked to see which spl_object_id the newly created object from makeInstance has and it always has the same object_id. Naturally all changes made after creation will be written in that very same object that was returned by makeInstance.
protected $streetno = '';
protected $addressinfo = '';
protected $mobile = '';
protected $salutation = '';
protected $svltcode = '';
protected $registerhash = '';
protected $birthdate = null;
protected $disable = false;
I don't know if makeInstance always behaved that way - we switched over from objectManager->get() - but I don't think it's logical, clean or proper to have a Factory like makeInstance return polluted objects. If anything, the Factory should clone from its object cache. I've used several frameworks and no framework has a Factory function that returns "old objects". They all clone from a classCache since that is faster than creating new instances.
Updated by Simon Gilli almost 4 years ago
Would be interesting if there is a difference for TYPO3v10 and v9. Since v10 most of the stuff here is done by Symfony containers.
Updated by Johannes Rebhan almost 4 years ago
I am trying to build an isolated test case, but that doesn't behave like the actual code. Meaning two clean calls to makeInstance create actual, clean new objects, but the same approach in the my practical example doesn't. Weirding out :D
Updated by Johannes Rebhan almost 4 years ago
Narrowed it down somewhat. If I create FeUser objects that are of my "homemade" model (extending FrontendUser class), storing those with the FeUserRepository (extending FrontendUserRepository), I get the same object back from makeInstance() everytime. When I switch out just the creation of the repository and the class to the core model class FrontendUser, I get a clean, fresh object everytime the makeInstance() is triggered. Both extensions are super simple. In the case of the Repository it adds one find function and in case of the Model it adds just a couple of class fields. No special hacks or mumbojumbo. I'll try to find out whats going on here...
Updated by Johannes Rebhan almost 4 years ago
It seems to have been a naming issue. Not sure why, but the fact that I named my class "FeUser" made the system stumble in unexpected ways. Once I renamed the class (without changing anything) to MyFeUser, makeInstance() suddenly started giving me clean, fresh objects back. Maybe it's connected to the naming of the table fe_user?
Updated by Johannes Rebhan almost 4 years ago
I've built a test extension to show the effect. I've went through the steps to reach the point at which makeInstance() starts outputting dirty objects. It happens exactly when I've added Configuration > Extbase > Persistence > Classes.php
<?php
return [
\Tester\TesterTestextension\Domain\Model\MyModel::class => ['tableName' => 'fe_users'],
];
Anyone who wants to try, or have a testcase for debugging the core:
composer require tester/tester-testextension "dev-master"
Then you just add the plugin, that comes with the package on any page and you'll get a chain of var_dumps that shows the identical spl_object_id for three objects created by consecutive calls to makeInstance(). You can also change those makeInstance() calls to anything else, that doesn't inherit from FrontendUser and you'll see three different ids in the output.
Updated by Johannes Rebhan almost 4 years ago
The object ends up being created by this part of makeInstance(). Seemingly the static container get() is not returning a clean instance, interpreting the object as Singleton? I am not really experienced with the Symfony internals. But they apparently fail to do the correct thing in this case.
if (self::$container !== null && $constructorArguments === [] && self::$container->has($className)) {
return self::$container->get($className);
}
Updated by Benjamin Franzke almost 4 years ago
Please post the Configuration/Services.{yaml,php} files of your extension.
It looks like you accidentally configured your Model as service.
TYPO3 Core/Symfony will not do that automatically.
It will be done by using `public: true` in Services.yaml, or by implementing interfaces (e.g singleton interface, extbase controller interface).
Models should not get a `public: true` statement (they are data objects and not services).
If you really, really, need that, than you need to configure `shared: false` as well for such model.
Updated by Johannes Rebhan almost 4 years ago
Issue is solved by setting public: false in Services.yaml as described by Benjamin. Thank you for the input!