Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch proxies to LazyGhostTrait #2700

Merged
merged 10 commits into from
Jan 22, 2025
Merged

Conversation

GromNaN
Copy link
Member

@GromNaN GromNaN commented Nov 23, 2024

Q A
Type improvement
BC Break no
Fixed issues Replace #2692

Summary

Use the LazyGhostTrait from symfony/var-exporter to generate lazy proxy classes.

@GromNaN GromNaN force-pushed the proxy-var-exporter-2 branch 4 times, most recently from 9bdb39c to a81c08c Compare November 23, 2024 21:36
@GromNaN GromNaN changed the title Switch proxies back to LazyGhostTrait Switch proxies to LazyGhostTrait Nov 23, 2024
use function substr;

/** @internal */
class LazyGhostProxyClassNameResolver implements ClassNameResolver, ProxyClassNameResolver
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @template T of object
* @template-extends Proxy<T>
*/
interface InternalProxy extends Proxy
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface is copied from Doctrine\ORM\Proxy\InternalProxy.

Comment on lines 589 to 608
$proxyManagerConfiguration = new ProxyManagerConfiguration();
$proxyManagerConfiguration->setProxiesTargetDir($this->getProxyDir());
$proxyManagerConfiguration->setProxiesNamespace($this->getProxyNamespace());

switch ($this->getAutoGenerateProxyClasses()) {
case self::AUTOGENERATE_FILE_NOT_EXISTS:
$proxyManagerConfiguration->setGeneratorStrategy(new FileWriterGeneratorStrategy(
new FileLocator($proxyManagerConfiguration->getProxiesTargetDir()),
));

break;
case self::AUTOGENERATE_EVAL:
$proxyManagerConfiguration->setGeneratorStrategy(new EvaluatingGeneratorStrategy());

break;
default:
throw new InvalidArgumentException('Invalid proxy generation strategy given - only AUTOGENERATE_FILE_NOT_EXISTS and AUTOGENERATE_EVAL are supported.');
}

return $proxyManagerConfiguration;
Copy link
Member Author

@GromNaN GromNaN Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating ProxyManagerConfiguration on-demand as the class is not required when LazyGhostObjects are used.

*
* @internal
*/
final class Autoloader
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is copied from Doctrine\ORM\Proxy\Autoloader.

Comment on lines +188 to +199
return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata, $factory): void {
$original = $persister->load([$classMetadata->identifier => $identifier], $proxy);

if (! $original && ! $factory->lifecycleEventManager->documentNotFound($proxy, $identifier)) {
throw DocumentNotFoundException::documentNotFound($classMetadata->getName(), $identifier);
}

// phpcs:ignore SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($proxy instanceof NotifyPropertyChanged) {
$proxy->addPropertyChangedListener($factory->uow);
}
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization code copied from StaticProxyFactory.

*
* @template T of object
*/
public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface;
public function getProxy(ClassMetadata $metadata, $identifier): object;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is modified, but child classes can be more restrictive. This is not a BC break for implementers of the interface, but it can be for users of the interface.

Comment on lines +120 to +123
public static function isLazyObject(object $document): bool
{
return $document instanceof InternalProxy || $document instanceof LazyLoadingInterface;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most test changes are related to check interfaces to assert if the class is a lazy proxy object. I wonder if this method should be in UOW.

Later, it will use ReflectionClass::getLazyInitializer() to detect native lazy object.

Comment on lines 620 to 655

/**
* Generate proxy classes using Symfony VarExporter's LazyGhostTrait if true.
* Otherwise, use ProxyManager's LazyLoadingGhostFactory (deprecated)
*/
public function setUseLazyGhostObject(bool $flag): void
{
if ($flag === false) {
if (! class_exists(ProxyManagerConfiguration::class)) {
throw new LogicException('Package "friendsofphp/proxy-manager-lts" is required to disable LazyGhostObject.');
}

trigger_deprecation(
'doctrine/mongodb-odm',
'2.6',
'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.',
);
}

$this->useLazyGhostObject = $flag;
}

public function isLazyGhostObjectEnabled(): bool
{
return $this->useLazyGhostObject ?? true;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the same method names as the ORM: isLazyGhostObjectEnabled and setLazyGhostObjectEnabled?

@GromNaN GromNaN requested review from alcaeus and malarzm November 23, 2024 22:32
@SenseException
Copy link
Member

Considering the unchecked checkboxes in your initial PR description, is this a WIP?

@GromNaN GromNaN force-pushed the proxy-var-exporter-2 branch 2 times, most recently from 631e341 to cbab97c Compare December 31, 2024 11:24
@GromNaN
Copy link
Member Author

GromNaN commented Dec 31, 2024

I've just rebased this PR.

The bundle will need an update to register the autoloader.

Regarding native lazy objects and prop hooks PR on ODM, I think the approaches are not conflicting this this PR.

@GromNaN GromNaN added this to the 2.10.0 milestone Jan 17, 2025
Copy link
Member

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor comments on this. I think the "BC Break" in the ProxyFactory interface is fine - proxies are an implementation detail and as long as we communicate that we change the default proxy mechanism we'll be fine. People shouldn't rely on a specific proxy behaviour, other than "it behaves as if it was an instance of the original class".

}

/** @deprecated */
public function getProxyManagerConfiguration(): ProxyManagerConfiguration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will return a new configuration instance every time this is called. I don't recall if there were any issues with that approach (e.g. due to unique identifiers in directories), so it's worth checking that creating a separate configuration doesn't cause the proxy manager to look for proxy files in different directories.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated to keep the instance in memory so that it's still possible to customize the configuration of the proxy manager directly. But it is reset each time a configuration is set on the ODM configuration class.

lib/Doctrine/ODM/MongoDB/Configuration.php Outdated Show resolved Hide resolved
phpstan-baseline.neon Show resolved Hide resolved
@GromNaN GromNaN force-pushed the proxy-var-exporter-2 branch from cbab97c to 0798779 Compare January 21, 2025 13:34
@GromNaN GromNaN force-pushed the proxy-var-exporter-2 branch from 0798779 to 4d4c864 Compare January 21, 2025 13:35
Copy link
Member

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM. @malarzm do you want to take another look or should we merge this for 2.10?

@GromNaN GromNaN merged commit 336a70e into doctrine:2.10.x Jan 22, 2025
19 checks passed
@GromNaN GromNaN deleted the proxy-var-exporter-2 branch January 22, 2025 12:18
@GromNaN
Copy link
Member Author

GromNaN commented Jan 22, 2025

I just found an incompatibility with DoctrineMongoDBODMBundle, the ProxyManager\Proxy\GhostObjectInterface interface is hardcoded. I think we have to keep lts-proxy-manager by default and add change the default configuration in a new version of DoctrineMongoDBODMBundle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants