Repository: symfony/symfony
File: src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php (PHP)

Code before:

    protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = [])
    {
        if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) {
            return false;
        }

        $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject;

        if ($context['_read_attributes'] ?? true) {
            if (!isset(self::$isReadableCache[$class.$attribute])) {
                self::$isReadableCache[$class.$attribute] = $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute) || (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute));
            }

            return self::$isReadableCache[$class.$attribute];
        }

        if (!isset(self::$isWritableCache[$class.$attribute])) {
            if (str_contains($attribute, '.')) {
                self::$isWritableCache[$class.$attribute] = true;
            } else {
                self::$isWritableCache[$class.$attribute] = $this->propertyInfoExtractor->isWritable($class, $attribute)
                || (
                    ($writeInfo = $this->writeInfoExtractor->getWriteInfo($class, $attribute)) && PropertyWriteInfo::TYPE_NONE !== $writeInfo->getType()
                    && (PropertyWriteInfo::TYPE_PROPERTY !== $writeInfo->getType() || PropertyWriteInfo::VISIBILITY_PUBLIC === $writeInfo->getVisibility())
                );
            }
        }

        return self::$isWritableCache[$class.$attribute];
    }

    private function hasAttributeAccessorMethod(string $class, string $attribute): bool
    {
        if (!isset(self::$reflectionCache[$class])) {
            self::$reflectionCache[$class] = new \ReflectionClass($class);
        }

        $reflection = self::$reflectionCache[$class];

        if (!$reflection->hasMethod($attribute)) {
            return false;
        }

        $method = $reflection->getMethod($attribute);

        return !$method->isStatic()
            && !$method->getAttributes(Ignore::class)
            && !$method->getNumberOfRequiredParameters();
    }
}

Review comment (refactor):
Wouldn't this make more sense? The visibility check should have already been done on L186, isn't it?

```suggestion
                || (($writeInfo = $this->writeInfoExtractor->getWriteInfo($class, $attribute)) && !\in_array($writeInfo->getType(), [PropertyWriteInfo::TYPE_NONE, PropertyWriteInfo::TYPE_PROPERTY], true));
```

Code after:

    protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = [])
    {
        if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) {
            return false;
        }

        $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject;

        if ($context['_read_attributes'] ?? true) {
            if (!isset(self::$isReadableCache[$class.$attribute])) {
                self::$isReadableCache[$class.$attribute] = $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute) || (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute));
            }

            return self::$isReadableCache[$class.$attribute];
        }

        return self::$isWritableCache[$class.$attribute] ??= str_contains($attribute, '.')
            || $this->propertyInfoExtractor->isWritable($class, $attribute)
            || !\in_array($this->writeInfoExtractor->getWriteInfo($class, $attribute)?->getType(), [null, PropertyWriteInfo::TYPE_NONE, PropertyWriteInfo::TYPE_PROPERTY], true);
    }

    private function hasAttributeAccessorMethod(string $class, string $attribute): bool
    {
        if (!isset(self::$reflectionCache[$class])) {
            self::$reflectionCache[$class] = new \ReflectionClass($class);
        }

        $reflection = self::$reflectionCache[$class];

        if (!$reflection->hasMethod($attribute)) {
            return false;
        }

        $method = $reflection->getMethod($attribute);

        return !$method->isStatic()
            && !$method->getAttributes(Ignore::class)
            && !$method->getNumberOfRequiredParameters();
    }
}