.../src/Normalizer/NormalizerBase.php | 53 ++++++++++++++++++++++ .../src/Kernel/NoFieldTypeNormalizersTest.php | 49 ++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/core/modules/serialization/src/Normalizer/NormalizerBase.php b/core/modules/serialization/src/Normalizer/NormalizerBase.php index 958aaf2..3f4e482 100644 --- a/core/modules/serialization/src/Normalizer/NormalizerBase.php +++ b/core/modules/serialization/src/Normalizer/NormalizerBase.php @@ -3,6 +3,13 @@ namespace Drupal\serialization\Normalizer; use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Field\FieldItemInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\field_normalization_test\Normalization\TextItemSillyNormalizer; +use Drupal\hal\Normalizer\FieldNormalizer as HalFieldNormalizer; +use Drupal\hal\Normalizer\FieldItemNormalizer as HalFieldItemNormalizer; +use Drupal\hal\Normalizer\EntityReferenceItemNormalizer as HalEntityReferenceItemNormalizer; +use Drupal\hal\Normalizer\TimestampItemNormalizer as HalTimestampItemNormalizer; use Drupal\rest\EventSubscriber\ResourceResponseSubscriber; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer; @@ -37,6 +44,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 +65,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 +106,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, + HalFieldNormalizer::class, + HalFieldItemNormalizer::class, + // Entity reference field normalizers for both normalizations. + EntityReferenceFieldItemNormalizer::class, + HalEntityReferenceItemNormalizer::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. + TextItemSillyNormalizer::class, + // TimestampItemNormalizer is being deprecated in https://www.drupal.org/project/drupal/issues/2926508. + TimestampItemNormalizer::class, + HalTimestampItemNormalizer::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..e65a841 --- /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()); + } + } + } + +}