.../src/Normalizer/NormalizerBase.php | 48 +++++++++++++++++++++ .../src/Kernel/NoFieldTypeNormalizersTest.php | 49 ++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/core/modules/serialization/src/Normalizer/NormalizerBase.php b/core/modules/serialization/src/Normalizer/NormalizerBase.php index 958aaf2..12e79b0 100644 --- a/core/modules/serialization/src/Normalizer/NormalizerBase.php +++ b/core/modules/serialization/src/Normalizer/NormalizerBase.php @@ -3,6 +3,8 @@ namespace Drupal\serialization\Normalizer; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Field\FieldItemInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\rest\EventSubscriber\ResourceResponseSubscriber; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer; @@ -37,6 +39,7 @@ public function supportsNormalization($data, $format = NULL) { } $supported = (array) $this->supportedInterfaceOrClass; + static::disallowFieldLevelNormalizers($supported, get_class($this)); return (bool) array_filter($supported, function ($name) use ($data) { return $data instanceof $name; @@ -57,6 +60,7 @@ public function supportsDenormalization($data, $type, $format = NULL) { } $supported = (array) $this->supportedInterfaceOrClass; + static::disallowFieldLevelNormalizers($supported, get_class($this)); $subclass_check = function ($name) use ($type) { return (class_exists($name) || interface_exists($name)) && is_subclass_of($type, $name, TRUE); @@ -97,4 +101,48 @@ protected function addCacheableDependency(array $context, $data) { } } + /** + * Triggers deprecation error for @FieldType-level normalizers, gives advice. + * + * @param array $supported + * The interface(s) or class(es) that this normalizer supports. + * @param $normalizer_class + * The class that implements this normalizer. + * + * @internal + */ + protected static function disallowFieldLevelNormalizers(array $supported, $normalizer_class) { + $allowed_exceptions = [ + // Generic field normalizers for both (default and 'hal') normalizations. + FieldNormalizer::class, + FieldItemNormalizer::class, + \Drupal\hal\Normalizer\FieldNormalizer::class, + \Drupal\hal\Normalizer\FieldItemNormalizer::class, + // Entity reference field normalizers for both normalizations. + EntityReferenceFieldItemNormalizer::class, + \Drupal\hal\Normalizer\EntityReferenceItemNormalizer::class, + // The NULL normalizer allows one to prevent listed classes from ever + // being normalized. By default, this is applied only tothe 'password' + // field type. + NullNormalizer::class, + // TextItemSillyNormalizer is a test-only normalizer. + \Drupal\field_normalization_test\Normalization\TextItemSillyNormalizer::class, + // TimestampItemNormalizer is being deprecated in https://www.drupal.org/project/drupal/issues/2926508. + TimestampItemNormalizer::class, + \Drupal\hal\Normalizer\TimestampItemNormalizer::class, + ]; + + $is_disallowed = function ($name) { + return in_array($name, [FieldItemInterface::class, FieldItemListInterface::class], TRUE) + || is_subclass_of($name, FieldItemInterface::class, TRUE) + || is_subclass_of($name, FieldItemListInterface::class, TRUE); + }; + + array_walk($supported, function ($name) use ($normalizer_class, $allowed_exceptions, $is_disallowed) { + if (!in_array($normalizer_class, $allowed_exceptions, TRUE) && $is_disallowed($name)) { + @trigger_error(sprintf('%s is a normalizer for a @FieldType plugin. This is very strongly discouraged because it requires similar logic to be implemented for every normalization. Please write this as a normalizer for a @DataType plugin instead, then it works for the entire Drupal API-First ecosystem!', $normalizer_class), E_USER_DEPRECATED); + } + }); + } + } diff --git a/core/modules/serialization/tests/src/Kernel/NoFieldTypeNormalizersTest.php b/core/modules/serialization/tests/src/Kernel/NoFieldTypeNormalizersTest.php new file mode 100644 index 0000000..8358837 --- /dev/null +++ b/core/modules/serialization/tests/src/Kernel/NoFieldTypeNormalizersTest.php @@ -0,0 +1,49 @@ +scan('module'); + $this->enableModules(array_keys($stable_core_modules)); + + $normalizer_service_ids = array_keys($this->container->findTaggedServiceIds('normalizer')); + foreach ($normalizer_service_ids as $service_id) { + // Violating normalizers that have been deprecated can be ignored. + if ($this->container->getDefinition($service_id)->isDeprecated()) { + continue; + } + + $normalizer = $this->container->get($service_id); + + // Calling ::supportsNormalization() or ::supportsDenormalization() will + // cause NormalizerBase::disallowFieldLevelNormalizers() to be called, + // which would trigger the E_USER_DEPRECATED error. + if ($normalizer instanceof NormalizerInterface) { + $normalizer->supportsNormalization(new \stdClass()); + } + if ($normalizer instanceof DenormalizerInterface) { + $normalizer->supportsDenormalization(TRUE, $this->randomMachineName()); + } + } + } + +}