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 back to Doctrine Persistence and LazyGhostTrait #2692

Open
wants to merge 14 commits into
base: 3.0.x
Choose a base branch
from
10 changes: 10 additions & 0 deletions UPGRADE-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ no longer implements the `PropertyChangedListener` interface.
`AnnotationDriver` class defined in `doctrine/persistence` (or in ODM's
compatibility layer)

## Proxy objects

The proxy implementation changed back to Doctrine proxies.
If you are checking for proxies, the following changed:
* Proxies no longer implement `ProxyManager\Proxy\GhostObjectInterface`.
To check whether a returned object is a proxy, check for the
`Doctrine\Persistence\Proxy` interface.
* The `initializeProxy` method has been replaced by `__load`.
* The `isProxyInitialized` method has been replaced by `__isInitialized`.

## Proxy Class Name Resolution

The `Doctrine\ODM\MongoDB\Proxy\Resolver\ClassNameResolver` interface has been
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
"doctrine/event-manager": "^1.0 || ^2.0",
"doctrine/instantiator": "^1.1 || ^2",
"doctrine/persistence": "^3.2",
"friendsofphp/proxy-manager-lts": "^1.0",
"jean85/pretty-package-versions": "^1.3.0 || ^2.0.1",
"mongodb/mongodb": "^1.17.0",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0",
"symfony/var-dumper": "^5.4 || ^6.0 || ^7.0"
"symfony/var-dumper": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.3.9 || ^7.0"
},
"require-dev": {
"ext-bcmath": "*",
Expand All @@ -48,6 +48,7 @@
"phpunit/phpunit": "^10.4",
"squizlabs/php_codesniffer": "^3.5",
"symfony/cache": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.2 || ^7.0",
"vimeo/psalm": "~5.24.0"
},
"conflict": {
Expand Down
93 changes: 35 additions & 58 deletions lib/Doctrine/ODM/MongoDB/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,14 @@
use Doctrine\ODM\MongoDB\PersistentCollection\DefaultPersistentCollectionGenerator;
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionFactory;
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionGenerator;
use Doctrine\ODM\MongoDB\Proxy\FileLocator;
use Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository;
use Doctrine\ODM\MongoDB\Repository\DefaultRepositoryFactory;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use InvalidArgumentException;
use MongoDB\Driver\WriteConcern;
use ProxyManager\Configuration as ProxyManagerConfiguration;
use ProxyManager\Factory\LazyLoadingGhostFactory;
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;

Expand All @@ -52,42 +46,51 @@
class Configuration
{
/**
* Never autogenerate a proxy/hydrator/persistent collection and rely that
* it was generated by some process before deployment. Copied from
* \Doctrine\Common\Proxy\AbstractProxyFactory.
* Never autogenerate proxy/hydrator/persistent collection and rely that it
* was generated by some process before deployment.
*/
public const AUTOGENERATE_NEVER = 0;

/**
* Always generates a new proxy/hydrator/persistent collection in every request.
*
* This is only sane during development.
* Copied from \Doctrine\Common\Proxy\AbstractProxyFactory.
*/
public const AUTOGENERATE_ALWAYS = 1;

/**
* Autogenerate the proxy/hydrator/persistent collection class when the file does not exist.
* Autogenerate the proxy/hydrator/persistent collection class when the
* proxy file does not exist.
*
* This strategy causes a file exists call whenever any proxy/hydrator is used the
* first time in a request. Copied from \Doctrine\Common\Proxy\AbstractProxyFactory.
* This strategy causes a file_exists() call whenever any proxy/hydrator is
* used the first time in a request.
*/
public const AUTOGENERATE_FILE_NOT_EXISTS = 2;

/**
* Generate the proxy/hydrator/persistent collection classes using eval().
*
* This strategy is only sane for development.
* Copied from \Doctrine\Common\Proxy\AbstractProxyFactory.
*/
public const AUTOGENERATE_EVAL = 3;

/**
* Autogenerate the proxy class when the proxy file does not exist or
* when the proxied file changed.
*
* This strategy causes a file_exists() call whenever any proxy is used the
* first time in a request. When the proxied file is changed, the proxy will
* be updated.
*/
public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4;

/**
* Array of attributes for this configuration instance.
*
* @psalm-var array{
* autoGenerateHydratorClasses?: self::AUTOGENERATE_*,
* autoGeneratePersistentCollectionClasses?: self::AUTOGENERATE_*,
* autoGenerateProxyClasses?: self::AUTOGENERATE_*,
* classMetadataFactoryName?: class-string<ClassMetadataFactoryInterface>,
* defaultCommitOptions?: CommitOptions,
* defaultDocumentRepositoryClassName?: class-string<ObjectRepository<object>>,
Expand All @@ -106,22 +109,21 @@ class Configuration
* persistentCollectionGenerator?: PersistentCollectionGenerator,
* persistentCollectionDir?: string,
* persistentCollectionNamespace?: string,
* proxyDir?: string,
* proxyNamespace?: string,
* repositoryFactory?: RepositoryFactory
* }
*/
private array $attributes = [];

private ?CacheItemPoolInterface $metadataCache = null;

private ProxyManagerConfiguration $proxyManagerConfiguration;

private int $autoGenerateProxyClasses = self::AUTOGENERATE_EVAL;

private bool $useTransactionalFlush = false;

public function __construct()
{
$this->proxyManagerConfiguration = new ProxyManagerConfiguration();
$this->setAutoGenerateProxyClasses(self::AUTOGENERATE_FILE_NOT_EXISTS);
}

Expand Down Expand Up @@ -248,27 +250,22 @@ public function setMetadataCache(CacheItemPoolInterface $cache): void
*/
public function setProxyDir(string $dir): void
{
$this->getProxyManagerConfiguration()->setProxiesTargetDir($dir);

// Recreate proxy generator to ensure its path was updated
if ($this->autoGenerateProxyClasses !== self::AUTOGENERATE_FILE_NOT_EXISTS) {
return;
}

$this->setAutoGenerateProxyClasses($this->autoGenerateProxyClasses);
$this->attributes['proxyDir'] = $dir;
}

/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*/
public function getProxyDir(): ?string
{
return $this->getProxyManagerConfiguration()->getProxiesTargetDir();
return $this->attributes['proxyDir'] ?? null;
}

/**
* Gets an int flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @return self::AUTOGENERATE_*
*/
public function getAutoGenerateProxyClasses(): int
{
Expand All @@ -279,37 +276,27 @@ public function getAutoGenerateProxyClasses(): int
* Sets an int flag that indicates whether proxy classes should always be regenerated
* during each script execution.
*
* @throws InvalidArgumentException If an invalid mode was given.
* @param bool|self::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*/
public function setAutoGenerateProxyClasses(int $mode): void
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
{
$this->autoGenerateProxyClasses = $mode;
$proxyManagerConfig = $this->getProxyManagerConfiguration();

switch ($mode) {
case self::AUTOGENERATE_FILE_NOT_EXISTS:
$proxyManagerConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy(
new FileLocator($proxyManagerConfig->getProxiesTargetDir()),
));

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

break;
default:
throw new InvalidArgumentException('Invalid proxy generation strategy given - only AUTOGENERATE_FILE_NOT_EXISTS and AUTOGENERATE_EVAL are supported.');
}
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
}

public function getProxyNamespace(): ?string
/**
* Gets the namespace where proxy classes reside.
*/
public function getProxyNamespace(): string|null
{
return $this->getProxyManagerConfiguration()->getProxiesNamespace();
return $this->attributes['proxyNamespace'] ?? null;
}

/**
* Sets the namespace where proxy classes reside.
*/
public function setProxyNamespace(string $ns): void
{
$this->getProxyManagerConfiguration()->setProxiesNamespace($ns);
$this->attributes['proxyNamespace'] = $ns;
}

public function setHydratorDir(string $dir): void
Expand Down Expand Up @@ -589,16 +576,6 @@ public function getPersistentCollectionGenerator(): PersistentCollectionGenerato
return $this->attributes['persistentCollectionGenerator'];
}

public function buildGhostObjectFactory(): LazyLoadingGhostFactory
{
return new LazyLoadingGhostFactory(clone $this->getProxyManagerConfiguration());
}

public function getProxyManagerConfiguration(): ProxyManagerConfiguration
{
return $this->proxyManagerConfiguration;
}

public function setUseTransactionalFlush(bool $useTransactionalFlush): void
{
$this->useTransactionalFlush = $useTransactionalFlush;
Expand Down
25 changes: 13 additions & 12 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver;
use Doctrine\ODM\MongoDB\Proxy\Resolver\ClassNameResolver;
use Doctrine\ODM\MongoDB\Proxy\Resolver\ProxyManagerClassNameResolver;
use Doctrine\ODM\MongoDB\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ODM\MongoDB\Proxy\InternalProxy;
use Doctrine\ODM\MongoDB\Proxy\ProxyFactory;
use Doctrine\ODM\MongoDB\Query\FilterCollection;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
Expand All @@ -29,7 +27,6 @@
use MongoDB\Database;
use MongoDB\Driver\ReadPreference;
use MongoDB\GridFS\Bucket;
use ProxyManager\Proxy\GhostObjectInterface;
use RuntimeException;
use Throwable;

Expand Down Expand Up @@ -133,7 +130,6 @@ class DocumentManager implements ObjectManager
*/
private ?FilterCollection $filterCollection = null;

/** @var ProxyClassNameResolver&ClassNameResolver */
private ProxyClassNameResolver $classNameResolver;

private static ?string $version = null;
Expand All @@ -157,7 +153,7 @@ protected function __construct(?Client $client = null, ?Configuration $config =
],
);

$this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config));
$this->classNameResolver = new DefaultProxyClassNameResolver();

$metadataFactoryClassName = $this->config->getClassMetadataFactoryName();
$this->metadataFactory = new $metadataFactoryClassName();
Expand All @@ -182,7 +178,12 @@ protected function __construct(?Client $client = null, ?Configuration $config =

$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
$this->proxyFactory = new StaticProxyFactory($this);
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses(),
);
$this->repositoryFactory = $this->config->getRepositoryFactory();
}

Expand Down Expand Up @@ -279,7 +280,7 @@ public function getSchemaManager(): SchemaManager
*
* @deprecated Fetch metadata for any class string (e.g. proxy object class) and read the class name from the metadata object
*/
public function getClassNameResolver(): ClassNameResolver
public function getClassNameResolver(): ProxyClassNameResolver
{
return $this->classNameResolver;
}
Expand Down Expand Up @@ -595,7 +596,7 @@ public function flush(array $options = []): void
* @param mixed $identifier
* @psalm-param class-string<T> $documentName
*
* @psalm-return T|(T&GhostObjectInterface<T>)
* @return T|(T&InternalProxy<T>)
*
* @template T of object
*/
Expand All @@ -612,7 +613,7 @@ public function getReference(string $documentName, $identifier): object
return $document;
}

/** @psalm-var T&GhostObjectInterface<T> $document */
/** @psalm-var T&InternalProxy<T> $document */
$document = $this->proxyFactory->getProxy($class, $identifier);
$this->unitOfWork->registerManaged($document, $identifier, []);

Expand Down
18 changes: 4 additions & 14 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
use Doctrine\ODM\MongoDB\Event\PreLoadEventArgs;
use Doctrine\ODM\MongoDB\Events;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Proxy\InternalProxy;
use Doctrine\ODM\MongoDB\Types\Type;
use Doctrine\ODM\MongoDB\UnitOfWork;
use ProxyManager\Proxy\GhostObjectInterface;

use function array_key_exists;
use function chmod;
Expand Down Expand Up @@ -448,19 +448,9 @@ public function hydrate(object $document, array $data, array $hints = []): array
}
}

if ($document instanceof GhostObjectInterface && $document->getProxyInitializer() !== null) {
// Inject an empty initialiser to not load any object data
$document->setProxyInitializer(static function (
GhostObjectInterface $ghostObject,
string $method, // we don't care
array $parameters, // we don't care
&$initializer,
array $properties, // we currently do not use this
): bool {
$initializer = null;

return true;
});
if ($document instanceof InternalProxy) {
// Skip initialization to not load any object data
$document->__setInitialized(true);
}

$data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints);
Expand Down
Loading
Loading