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

Make ServiceEntityRepository lazy on Symfony 6.2+ #1599

Merged
merged 2 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Repository/LazyServiceEntityRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use LogicException;
use Symfony\Component\VarExporter\LazyGhostTrait;

use function sprintf;

/**
* Optional EntityRepository base class with a simplified constructor (for autowiring).
*
* To use in your class, inject the "registry" service and call
* the parent constructor. For example:
*
* class YourEntityRepository extends ServiceEntityRepository
* {
* public function __construct(ManagerRegistry $registry)
* {
* parent::__construct($registry, YourEntity::class);
* }
* }
*
* @internal to be renamed ServiceEntityRepository when PHP 8.1 / Symfony 6.2 becomes required
*
* @template T of object
* @template-extends EntityRepository<T>
*/
class LazyServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
use LazyGhostTrait;

/**
* @param string $entityClass The class name of the entity this repository manages
* @psalm-param class-string<T> $entityClass
*/
public function __construct(ManagerRegistry $registry, string $entityClass)
{
$initializer = function ($instance, $property) use ($registry, $entityClass) {
$manager = $registry->getManagerForClass($entityClass);

if ($manager === null) {
throw new LogicException(sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.',
$entityClass
));
}

parent::__construct($manager, $manager->getClassMetadata($entityClass));

return $this->$property;
};

self::createLazyGhost([
"\0*\0_em" => $initializer,
"\0*\0_class" => $initializer,
"\0*\0_entityName" => $initializer,
], null, $this);
}
}
68 changes: 38 additions & 30 deletions Repository/ServiceEntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,51 @@
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use LogicException;
use Symfony\Component\VarExporter\LazyGhostTrait;

use function sprintf;
use function trait_exists;

/**
* Optional EntityRepository base class with a simplified constructor (for autowiring).
*
* To use in your class, inject the "registry" service and call
* the parent constructor. For example:
*
* class YourEntityRepository extends ServiceEntityRepository
* {
* public function __construct(ManagerRegistry $registry)
* {
* parent::__construct($registry, YourEntity::class);
* }
* }
*
* @template T of object
* @template-extends EntityRepository<T>
*/
class ServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
if (trait_exists(LazyGhostTrait::class)) {
class ServiceEntityRepository extends LazyServiceEntityRepository
{
}
} else {
/**
* @param string $entityClass The class name of the entity this repository manages
* @psalm-param class-string<T> $entityClass
* Optional EntityRepository base class with a simplified constructor (for autowiring).
*
* To use in your class, inject the "registry" service and call
* the parent constructor. For example:
*
* class YourEntityRepository extends ServiceEntityRepository
* {
* public function __construct(ManagerRegistry $registry)
* {
* parent::__construct($registry, YourEntity::class);
* }
* }
*
* @template T of object
* @template-extends EntityRepository<T>
*/
public function __construct(ManagerRegistry $registry, string $entityClass)
class ServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
$manager = $registry->getManagerForClass($entityClass);
/**
* @param string $entityClass The class name of the entity this repository manages
* @psalm-param class-string<T> $entityClass
*/
public function __construct(ManagerRegistry $registry, string $entityClass)
{
$manager = $registry->getManagerForClass($entityClass);

if ($manager === null) {
throw new LogicException(sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.',
$entityClass
));
}
if ($manager === null) {
throw new LogicException(sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.',
$entityClass
));
}

parent::__construct($manager, $manager->getClassMetadata($entityClass));
parent::__construct($manager, $manager->getClassMetadata($entityClass));
}
}
}
3 changes: 2 additions & 1 deletion Tests/Repository/ServiceEntityRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function testConstructorThrowsExceptionWhenNoManagerFound(): void
EXCEPTION
);
/** @psalm-suppress UndefinedClass */
new ServiceEntityRepository($registry, TestEntity::class);
$repo = new ServiceEntityRepository($registry, TestEntity::class);
$repo->getClassName();
}
}
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>Tests/*</exclude-pattern>
<exclude-pattern>Repository/ServiceEntityRepository.php</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ClassFileName.NoMatch">
<exclude-pattern>Tests/*</exclude-pattern>
Expand Down
13 changes: 13 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<!-- We use the "Foo" namespace in unit tests. We are aware that those classes don't exist. -->
<referencedClass name="Foo\*"/>
<referencedClass name="Symfony\Bridge\Doctrine\Attribute\MapEntity"/>
<referencedClass name="Symfony\Component\VarExporter\LazyGhostTrait"/>
<referencedClass name="Symfony\Component\VarExporter\LazyObjectInterface"/>
</errorLevel>
</UndefinedClass>
Expand All @@ -62,5 +63,17 @@
<directory name="Tests/DependencyInjection"/>
</errorLevel>
</UndefinedDocblockClass>
<UndefinedTrait>
<errorLevel type="suppress">
<!-- Consumer is meant to check if trait exists before using this class -->
<file name="Repository/LazyServiceEntityRepository.php"/>
</errorLevel>
</UndefinedTrait>
<DuplicateClass>
<errorLevel type="suppress">
<!-- Conditional class definition-->
<file name="Repository/ServiceEntityRepository.php"/>
</errorLevel>
</DuplicateClass>
</issueHandlers>
</psalm>