Skip to content

PHP 8.4 Compatibility: Closure returned instead of Icon/ArrayObject #3157

@mjrsanders

Description

@mjrsanders

PHP 8.4 Compatibility Issue in Symfony UX Icons v2.31.0

Description

After upgrading to PHP 8.4, Symfony UX Icons throws a TypeError when attempting to render icons or warm the icon cache.

Error Messages

Template Rendering Error:

Symfony\UX\Icons\Registry\CacheIconRegistry::get(): Return value must be of type Symfony\UX\Icons\Icon, Closure returned

Cache Warming Error:

TypeError: Cannot assign Closure to property Symfony\UX\Icons\Iconify::$sets of type ArrayObject at vendor/symfony/ux-icons/src/Iconify.php:204

Environment

  • PHP Version: 8.4.0
  • Symfony UX Icons Version: v2.31.0
  • Symfony Version: 7.3

Root Cause

PHP 8.4 enforces stricter type checking. When using Symfony's cache component with the get() method and a callback, the cache is returning the Closure itself instead of executing it and returning the result. This causes type mismatches when trying to assign the Closure to typed properties (ArrayObject) or return it from typed methods (Icon).

The issue occurs in two files:

  1. src/Registry/CacheIconRegistry.php - line 37-41
  2. src/Iconify.php - line 204

Reproduction Steps

  1. Upgrade to PHP 8.4
  2. Use composer update to install/update Symfony UX Icons
  3. Attempt to render an icon in a Twig template: <twig:ux:icon name="bi:bell-fill" />
  4. OR run: php bin/console ux:icons:warm-cache

Both will fail with the errors mentioned above.

Proposed Fix

File: src/Registry/CacheIconRegistry.php

Before:

public function get(string $name, bool $refresh = false): Icon
{
    if (!Icon::isValidName($name)) {
        throw new IconNotFoundException(\sprintf('The icon name "%s" is not valid.', $name));
    }

    return $this->cache->get(
        Icon::nameToId($name),
        fn () => $this->inner->get($name),
        beta: $refresh ? \INF : null,
    );
}

After:

public function get(string $name, bool $refresh = false): Icon
{
    if (!Icon::isValidName($name)) {
        throw new IconNotFoundException(\sprintf('The icon name "%s" is not valid.', $name));
    }

    $result = $this->cache->get(
        Icon::nameToId($name),
        fn () => $this->inner->get($name),
        beta: $refresh ? \INF : null,
    );
    
    // PHP 8.4 compatibility: ensure we return Icon, not Closure
    return $result instanceof \Closure ? $result() : $result;
}

File: src/Iconify.php

Before:

private function sets(): \ArrayObject
{
    return $this->sets ??= $this->cache->get('iconify-sets', function () {
        $response = $this->http()->request('GET', '/collections');

        return new \ArrayObject($response->toArray());
    });
}

After:

private function sets(): \ArrayObject
{
    if (!isset($this->sets)) {
        $result = $this->cache->get('iconify-sets', function () {
            $response = $this->http()->request('GET', '/collections');

            return new \ArrayObject($response->toArray());
        });
        
        // PHP 8.4 compatibility: ensure we get ArrayObject, not Closure
        $this->sets = $result instanceof \Closure ? $result() : $result;
    }

    return $this->sets;
}

Testing

After applying these fixes:

  1. Icons render correctly in templates
  2. php bin/console ux:icons:warm-cache completes successfully
  3. No performance impact observed
  4. Backwards compatible with PHP 8.1-8.3

Additional Notes

This fix handles the case where the cache returns a Closure by detecting it and executing it to get the actual value. This maintains compatibility with both the current behavior and potential future fixes in Symfony's cache component.

php84-compatibility.patch

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions