Integrate symfony/validator with Doctrine entitites

[MAILPOET-2437]
This commit is contained in:
Jan Jakeš
2019-10-22 10:19:25 +02:00
committed by Jack Kitterhing
parent bed49f97ef
commit 730f640cc3
7 changed files with 117 additions and 5 deletions

View File

@@ -117,6 +117,7 @@ class ContainerConfigurator implements IContainerConfigurator {
->setFactory([new Reference(\MailPoet\Doctrine\EntityManagerFactory::class), 'createEntityManager'])
->setPublic(true);
$container->autowire(\MailPoet\Doctrine\EventListeners\TimestampListener::class);
$container->autowire(\MailPoet\Doctrine\EventListeners\ValidationListener::class);
// Dynamic segments
$container->autowire(\MailPoet\DynamicSegments\DynamicSegmentHooks::class);
// Cron

View File

@@ -2,6 +2,7 @@
namespace MailPoet\Doctrine;
use MailPoetVendor\Doctrine\Common\Annotations\AnnotationRegistry;
use MailPoetVendor\Doctrine\Common\Annotations\SimpleAnnotationReader;
use MailPoetVendor\Doctrine\Common\Cache\ArrayCache;
use MailPoetVendor\Doctrine\Common\Proxy\AbstractProxyFactory;
@@ -39,6 +40,10 @@ class ConfigurationFactory {
$metadata_storage = new MetadataCache(self::METADATA_DIR);
$configuration->setMetadataCacheImpl($metadata_storage);
// autoload all annotation classes using registered loaders (Composer)
// (needed for Symfony\Validator constraint annotations to be loaded)
AnnotationRegistry::registerLoader('class_exists');
// register annotation reader if doctrine/annotations package is installed
// (i.e. in dev environment, on production metadata is dumped in the build)
if (class_exists(SimpleAnnotationReader::class)) {

View File

@@ -3,6 +3,7 @@
namespace MailPoet\Doctrine;
use MailPoet\Doctrine\EventListeners\TimestampListener;
use MailPoet\Doctrine\EventListeners\ValidationListener;
use MailPoet\Tracy\DoctrinePanel\DoctrinePanel;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\ORM\Configuration;
@@ -18,27 +19,42 @@ class EntityManagerFactory {
/** @var Configuration */
private $configuration;
/** @var TimestampListener */
private $timestamp_listener;
function __construct(Connection $connection, Configuration $configuration, TimestampListener $timestamp_listener) {
/** @var ValidationListener */
private $validation_listener;
function __construct(
Connection $connection,
Configuration $configuration,
TimestampListener $timestamp_listener,
ValidationListener $validation_listener
) {
$this->connection = $connection;
$this->configuration = $configuration;
$this->timestamp_listener = $timestamp_listener;
$this->validation_listener = $validation_listener;
}
function createEntityManager() {
$entity_manager = EntityManager::create($this->connection, $this->configuration);
$this->setupTimestampListener($entity_manager);
$this->setupListeners($entity_manager);
if (class_exists(Debugger::class)) {
DoctrinePanel::init($entity_manager);
}
return $entity_manager;
}
private function setupTimestampListener(EntityManager $entity_manager) {
private function setupListeners(EntityManager $entity_manager) {
$entity_manager->getEventManager()->addEventListener(
[Events::prePersist, Events::preUpdate],
$this->timestamp_listener
);
$entity_manager->getEventManager()->addEventListener(
[Events::onFlush],
$this->validation_listener
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace MailPoet\Doctrine\EventListeners;
use MailPoet\Doctrine\ValidationException;
use MailPoetVendor\Doctrine\ORM\Event\OnFlushEventArgs;
use MailPoetVendor\Symfony\Component\Validator\Validation;
use MailPoetVendor\Symfony\Component\Validator\Validator\ValidatorInterface;
class ValidationListener {
/** @var ValidatorInterface */
private $validator;
function __construct() {
$this->validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
}
function onFlush(OnFlushEventArgs $event_args) {
$unit_of_work = $event_args->getEntityManager()->getUnitOfWork();
foreach ($unit_of_work->getScheduledEntityInsertions() as $entity) {
$this->validate($entity);
}
foreach ($unit_of_work->getScheduledEntityUpdates() as $entity) {
$this->validate($entity);
}
}
private function validate($entity) {
$violations = $this->validator->validate($entity);
if ($violations->count() > 0) {
throw new ValidationException(get_class($entity), $violations);
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace MailPoet\Doctrine;
use MailPoetVendor\Symfony\Component\Validator\ConstraintViolationInterface;
use MailPoetVendor\Symfony\Component\Validator\ConstraintViolationListInterface;
class ValidationException extends \RuntimeException {
/** @var string */
private $resource_name;
/** @var ConstraintViolationListInterface|ConstraintViolationInterface[] */
private $violations;
function __construct($resource_name, ConstraintViolationListInterface $violations) {
$this->resource_name = $resource_name;
$this->violations = $violations;
$line_prefix = ' ';
$message = "Validation failed for '$resource_name'.\nDetails:\n";
$message .= $line_prefix . implode("\n$line_prefix", $this->getErrors());
parent::__construct($message);
}
/** @return string */
function getResourceName() {
return $this->resource_name;
}
/** @return ConstraintViolationListInterface|ConstraintViolationInterface[] */
function getViolations() {
return $this->violations;
}
/** @return string[] */
function getErrors() {
$messages = [];
foreach ($this->violations as $violation) {
$messages[] = $this->formatError($violation);
}
sort($messages);
return $messages;
}
private function formatError(ConstraintViolationInterface $violation) {
return '[' . $violation->getPropertyPath() . '] ' . $violation->getMessage();
}
}

View File

@@ -6,6 +6,7 @@ use Carbon\Carbon;
use MailPoet\Doctrine\ConfigurationFactory;
use MailPoet\Doctrine\EntityManagerFactory;
use MailPoet\Doctrine\EventListeners\TimestampListener;
use MailPoet\Doctrine\EventListeners\ValidationListener;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Doctrine\Common\Cache\ArrayCache;
@@ -84,7 +85,8 @@ class TimestampListenerTest extends \MailPoetTest {
$configuration->setMetadataCacheImpl(new ArrayCache());
$timestamp_listener = new TimestampListener($this->wp);
$entity_manager_factory = new EntityManagerFactory($this->connection, $configuration, $timestamp_listener);
$validation_listener = new ValidationListener();
$entity_manager_factory = new EntityManagerFactory($this->connection, $configuration, $timestamp_listener, $validation_listener);
return $entity_manager_factory->createEntityManager();
}
}

View File

@@ -6,6 +6,7 @@ use Exception;
use MailPoet\Doctrine\ConfigurationFactory;
use MailPoet\Doctrine\EntityManagerFactory;
use MailPoet\Doctrine\EventListeners\TimestampListener;
use MailPoet\Doctrine\EventListeners\ValidationListener;
use MailPoet\Test\Doctrine\Types\JsonEntity;
use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Doctrine\Common\Cache\ArrayCache;
@@ -158,7 +159,8 @@ class JsonTypesTest extends \MailPoetTest {
$configuration->setMetadataCacheImpl(new ArrayCache());
$timestamp_listener = new TimestampListener($this->wp);
$entity_manager_factory = new EntityManagerFactory($this->connection, $configuration, $timestamp_listener);
$validation_listener = new ValidationListener();
$entity_manager_factory = new EntityManagerFactory($this->connection, $configuration, $timestamp_listener, $validation_listener);
return $entity_manager_factory->createEntityManager();
}
}