Skip to content

Application crashes with ReflectionException when oro_entity_config contains stale class references (missing PHP classes after branch switch) #1141

@Genaker

Description

@Genaker

Summary

php bin/console cache:clear crashes with a ReflectionException when the oro_entity_config
database table contains entity class names that no longer exist as PHP files.
This happens naturally when switching git branches where different bundles are present.
The error completely blocks cache clearing with no hint about the cause or fix.

Steps to reproduce

  1. Install Oro with a custom bundle (e.g. AcmeShipmentBundle) that has entities registered
    in oro_entity_config via migrations
  2. Switch to a git branch that does not include that bundle
  3. Run composer dump-autoload
  4. Run php bin/console cache:clear

Actual Result

Cache clear aborts with:

In AbstractManagerRegistry.php line 175:

  Class "Acme\Bundle\ShipmentBundle\Entity\Delivery" does not exist

Full stack trace:

ReflectionClass->__construct()
  at vendor/doctrine/persistence/src/Persistence/AbstractManagerRegistry.php:175
Doctrine\Persistence\AbstractManagerRegistry->getManagerForClass()
  at vendor/oro/platform/src/Oro/Bundle/EntityBundle/ORM/Registry.php:79
Oro\Bundle\EntityBundle\ORM\Registry->getManagerForClass()
  at vendor/oro/marketing/src/Oro/Bundle/MarketingListBundle/Provider/ContactInformationExclusionProvider.php:56
Oro\Bundle\MarketingListBundle\Provider\ContactInformationExclusionProvider->isIgnoredEntity()
  at vendor/oro/platform/src/Oro/Bundle/EntityBundle/Provider/ChainExclusionProvider.php:37
Oro\Bundle\EntityBundle\Provider\ChainExclusionProvider->isIgnoredEntity()
  at vendor/oro/marketing/src/Oro/Bundle/MarketingActivityBundle/Provider/MarketingActivityVirtualRelationProvider.php:66
Oro\Bundle\MarketingActivityBundle\Provider\MarketingActivityVirtualRelationProvider->getVirtualRelations()
  at vendor/oro/platform/src/Oro/Bundle/EntityBundle/Provider/ChainVirtualRelationProvider.php:56
Oro\Bundle\EntityBundle\Provider\ChainVirtualRelationProvider->getVirtualRelations()
  at vendor/oro/platform/src/Oro/Bundle/EntityConfigBundle/Config/ConfigCacheWarmer.php:285
Oro\Bundle\EntityConfigBundle\Config\ConfigCacheWarmer->loadVirtualFields()
  at vendor/oro/platform/src/Oro/Bundle/EntityConfigBundle/Config/ConfigCacheWarmer.php:93
Oro\Bundle\EntityConfigBundle\Config\ConfigCacheWarmer->warmUpCache()
  at vendor/oro/platform/src/Oro/Bundle/EntityConfigBundle/Cache/CacheWarmer.php:21

Root cause: ConfigCacheWarmer::loadVirtualFields() iterates every class in oro_entity_config
and calls Registry::getManagerForClass($class) on each one.
That method delegates to AbstractManagerRegistry which calls new ReflectionClass($class)
with no class_exists() guard. When $class no longer exists, PHP throws a
ReflectionException and aborts the entire warmup.

// Oro\Bundle\EntityBundle\ORM\Registry — no guard before delegating to parent
public function getManagerForClass($class)
{
    if (\array_key_exists($class, $this->managersMap)) {
        return $this->cachedManagers[$this->managersMap[$class]];
    }
    $manager = parent::getManagerForClass($class); // crashes if class is missing
    ...
}

// Doctrine\Persistence\AbstractManagerRegistry — detonator
public function getManagerForClass(string $class)
{
    $proxyClass = new ReflectionClass($class); // ReflectionException if class gone
    ...
}

Expected Result

cache:clear should complete successfully.
A class that no longer exists has no Doctrine manager — getManagerForClass() should
return null for it (consistent with its existing contract for unknown classes)
rather than throwing.

Suggested fix in Oro\Bundle\EntityBundle\ORM\Registry::getManagerForClass():

public function getManagerForClass($class)
{
    if (\array_key_exists($class, $this->managersMap)) {
        return $this->cachedManagers[$this->managersMap[$class]];
    }

    // Guard: oro_entity_config is DB-driven and can contain stale class references
    // (e.g. after switching git branches or removing a bundle).
    // Return null instead of crashing — no manager exists for a non-existent class.
    if (!class_exists($class) && !interface_exists($class, false)) {
        $this->managersMap[$class] = '';
        $this->cachedManagers[''] = null;
        return null;
    }

    $manager = parent::getManagerForClass($class);
    $hash    = null !== $manager ? spl_object_hash($manager) : '';
    $this->managersMap[$class]   = $hash;
    $this->cachedManagers[$hash] = $manager;

    return $manager;
}

This fix belongs in Oro\Bundle\EntityBundle\ORM\Registry (Oro's own code, not Doctrine)
because Oro drives getManagerForClass() from a database-sourced class list
(oro_entity_config) that can legitimately contain stale references.
Doctrine's expectation that callers pass valid class names is reasonable;
Oro must guard its own DB-driven usage.

Details about your environment

  • OroPlatform version: 6.1.0
  • PHP version: 8.2
  • Database: PostgreSQL 14

Additional information

Workaround until fixed — remove the stale rows manually:

SELECT id, class_name FROM oro_entity_config WHERE class_name LIKE '%RemovedBundleName%';
DELETE FROM oro_entity_config WHERE class_name LIKE '%RemovedBundleName%';

Then: composer dump-autoload && php bin/console cache:clear

This issue affects any developer doing routine branch switching across feature branches
that add or remove bundles — a very common workflow on multi-team Oro projects.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions