From 1002867f8c5fc85a9dc33ad10a153fb6b0934fd8 Mon Sep 17 00:00:00 2001 From: Abdellah Ramadan Date: Sun, 24 Aug 2025 13:48:07 +0100 Subject: [PATCH] Add singleton --- README.md | 1 + config/services.php | 6 +++ docs/singleton.md | 19 ++++++++ src/Common/Attributes/Singleton.php | 20 ++++++++ src/Entity/Traits/TreeTrait.php | 8 ++++ .../Singleton/SingletonListener.php | 47 +++++++++++++++++++ src/Exceptions/EntityCountException.php | 22 +++++++++ 7 files changed, 123 insertions(+) create mode 100644 docs/singleton.md create mode 100644 src/Common/Attributes/Singleton.php create mode 100644 src/Entity/Traits/TreeTrait.php create mode 100644 src/EventListener/Singleton/SingletonListener.php create mode 100644 src/Exceptions/EntityCountException.php diff --git a/README.md b/README.md index 6e83467..c8ae7eb 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A set of Doctrine Entity features to automate repetitive entity tasks and ease d - [x] [UUID](docs/uuid.md) - [x] [IP Tagged](docs/ip_tagged.md) - [x] [Soft Delete](docs/soft_delete.md) +- [x] [Singleton](docs/singleton.md) - [ ] Logging - [ ] Tree - [ ] Translation diff --git a/config/services.php b/config/services.php index b5850c8..138df59 100644 --- a/config/services.php +++ b/config/services.php @@ -6,6 +6,7 @@ use Rami\EntityKitBundle\EventListener\Authored\AuthoredListener; use Rami\EntityKitBundle\EventListener\IpTagged\IpTaggedListener; use Rami\EntityKitBundle\EventListener\LoggedEntity\LoggedEntityListener; +use Rami\EntityKitBundle\EventListener\Singleton\SingletonListener; use Rami\EntityKitBundle\EventListener\Slugged\SluggedListener; use Rami\EntityKitBundle\EventListener\SoftDelete\SoftDeleteFilterListener; use Rami\EntityKitBundle\EventListener\TimeStamped\TimeStampedListener; @@ -60,4 +61,9 @@ ->set(SoftDeleteFilterListener::class) ->args([new Reference(ManagerRegistry::class), new Reference(ParameterBagInterface::class)]) ->tag('kernel.event_listener', ['event' => 'kernel.controller', 'method' => 'onKernelController']); + + $services + ->set(SingletonListener::class) + ->args([new Reference(ManagerRegistry::class)]) + ->tag('doctrine.event_listener', ['event' => 'prePersist']); }; \ No newline at end of file diff --git a/docs/singleton.md b/docs/singleton.md new file mode 100644 index 0000000..dcecf48 --- /dev/null +++ b/docs/singleton.md @@ -0,0 +1,19 @@ +Singleton +- +Add a singleton entity where only one entity can be created or new entities cannot be created if one or more already exists. + +This is good for an app configuration saved in the database + +use the `Singleton` attribute on your entity class + +```php +use Rami\EntityKitBundle\Common\Attributes\Singleton + +#[Singleton] +class Blog +{ + // properties omitted +} +``` + +This prevents new entities of type `Blog` from being created. \ No newline at end of file diff --git a/src/Common/Attributes/Singleton.php b/src/Common/Attributes/Singleton.php new file mode 100644 index 0000000..42199ce --- /dev/null +++ b/src/Common/Attributes/Singleton.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, + * please view the LICENSE file that was distributed with this source code. + */ + +namespace Rami\EntityKitBundle\Common\Attributes; + +use Attribute; + +#[\Attribute(Attribute::TARGET_CLASS)] +class Singleton +{ + +} \ No newline at end of file diff --git a/src/Entity/Traits/TreeTrait.php b/src/Entity/Traits/TreeTrait.php new file mode 100644 index 0000000..53dcc02 --- /dev/null +++ b/src/Entity/Traits/TreeTrait.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, + * please view the LICENSE file that was distributed with this source code. + */ + +namespace Rami\EntityKitBundle\EventListener\Singleton; + +use Doctrine\ORM\Event\PrePersistEventArgs; +use Doctrine\Persistence\ManagerRegistry; +use JetBrains\PhpStorm\NoReturn; +use Rami\EntityKitBundle\Common\Attributes\Singleton; +use Rami\EntityKitBundle\Exceptions\EntityCountException; + +class SingletonListener +{ + + public function __construct(private ManagerRegistry $managerRegistry) + { + } + + /** + * @throws EntityCountException + */ + #[NoReturn] public function prePersist(PrePersistEventArgs $args): void + { + $entity = $args->getObject(); + + $reflection = new \ReflectionObject($entity); + $attributes = $reflection->getAttributes(Singleton::class); + + if (count($attributes) === 0) { + return; + } + + $existingEntity = $this->managerRegistry->getManager()->getRepository(get_class($entity))->findOneBy([]); + + if (null !== $existingEntity) { + throw new EntityCountException(); + } + } +} \ No newline at end of file diff --git a/src/Exceptions/EntityCountException.php b/src/Exceptions/EntityCountException.php new file mode 100644 index 0000000..c7d099c --- /dev/null +++ b/src/Exceptions/EntityCountException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, + * please view the LICENSE file that was distributed with this source code. + */ + +namespace Rami\EntityKitBundle\Exceptions; + +use Symfony\Component\HttpFoundation\Response; + +class EntityCountException extends \Exception +{ + protected $code = Response::HTTP_CONFLICT; + + protected $message = 'This entity is a singleton. Entity has already been created.'; + +} \ No newline at end of file