Skip to content

OrganizationProBundle.is_global Extended Entity Initialization Race Condition #1135

@Genaker

Description

@Genaker

Summary

During fresh Oro Platform installation (specifically when running bin/console oro:migration:load --force or bin/console oro:platform:update --force), the initialization process fails with a Doctrine QueryException when the entity config cache warmup step tries to query the Organization.is_global field. The field should have been added by the OrganizationProBundle installer, but Doctrine's extended entity metadata has not been synchronized yet, causing "field doesn't exist" semantic errors.

This is a classic initialization order race condition affecting fresh installations on PHP 8.4+ where strict type checking is enforced.


Steps to reproduce

  1. Start with a clean PostgreSQL database (no tables)
  2. Restore an Oro 6.1 EE backup from before extended entity initialization (or start from scratch)
  3. Ensure var/installed marker exists to skip the web installer:
    touch var/installed
  4. Clear cache and proxies:
    rm -rf var/cache/* var/doctrine_proxies/*
  5. Run migrations with verbosity to see the error:
    bin/console oro:migration:load --force -vvv

Why This Step Triggers the Bug:
The oro:migration:load command executes all pending migrations. One of these migrations is WarmUpEntityConfigCacheMigration from EntityConfigBundle, which internally runs the oro:entity-config:cache:warmup command. This is a migration query, not a separate step. The error occurs during the migration sequence.


Actual Result

During step 5, the migration sequence fails at the WarmUpEntityConfigCacheMigration with:

[RuntimeException] Failed migrations: Oro\Bundle\EntityConfigBundle\Migration\WarmUpEntityConfigCacheMigration

In QueryException.php line 23:

  [Doctrine\ORM\Query\QueryException]
  SELECT org FROM Oro\Bundle\OrganizationBundle\Entity\Organization org WHERE org.is_global = :isGlobal

Exception trace:
  at /oro-ee/vendor/doctrine/orm/lib/Doctrine/ORM/Query/QueryException.php:23
  Doctrine\ORM\Query\QueryException::dqlError() at /oro-ee/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php:495
  ...
  [Semantical Error] Error: Class Oro\Bundle\OrganizationBundle\Entity\Organization has no field or association named is_global

The entire migration sequence stops, blocking the installation. No further migrations run after this failure.


Expected Result

The platform initialization should complete successfully even on fresh installations. The extended entity field is_global should either:

  1. Be available to query by the time entity config cache warmup runs, OR
  2. Be handled gracefully if not yet synchronized at that point in initialization

A fresh installation should not fail due to uninitialized extended entity metadata.


Details about your environment

  • OroPlatform version: 6.1.0 EE
  • OroPlatformEnterprise version: 6.1.0 EE
  • PHP version: 8.4.14+ (strict type checking enforced)
  • Database: PostgreSQL 13.7+
  • Server operating system: Linux (Ubuntu 24.04+, Docker container)

Root Cause Analysis

The Initialization Sequence Problem

  1. Migration starts: oro:migration:load command executes

  2. OrganizationProBundle installer runs (via OroOrganizationProBundleInstaller.php):

    • Adds is_global column to oro_organization table (lines 48-64)
    • Marks the Organization entity as "is_global aware"
    • Configures extended entity metadata (ExtendScope, OroOptions, etc.)
  3. Entity config cache warmup runs (via WarmUpEntityConfigCacheMigration):

    • Calls oro:entity-config:cache:warmup command
    • This discovers and indexes all extended entity configurations
    • Various helper classes query the Organization entity for is_global field:
      • OrganizationProHelper::getGlobalOrganization() at line 54
      • OwnershipProConditionDataBuilder::getGlobalOrganizationId() at line 110
  4. Doctrine metadata mismatch:

    • The database column exists (added in step 2)
    • But Doctrine's in-memory metadata hasn't been synchronized
    • Doctrine still thinks Organization has no is_global field
    • DQL parser throws semantic error: "Class has no field named is_global"

Why This Happens

The extended entity metadata synchronization happens after the entity config cache is warmed up, but the code tries to query the field during warmup. This creates a race condition where:

  • The schema is updated (column added)
  • The Doctrine metadata is NOT updated (field not in entity class metadata yet)
  • Queries fail because Doctrine doesn't know about the new field

Affected Code Files

Primary Locations (Query Failures):

  • vendor/oro/platform-enterprise/src/Oro/Bundle/OrganizationProBundle/Helper/OrganizationProHelper.php (line 54)

    ->where('org.is_global = :isGlobal')
  • vendor/oro/platform-enterprise/src/Oro/Bundle/SecurityProBundle/ORM/Walker/OwnershipProConditionDataBuilder.php (line 110)

    ->findOneBy(['is_global' => true]);

Extended Entity Definition:

  • vendor/oro/platform-enterprise/src/Oro/Bundle/OrganizationProBundle/Migrations/Schema/OroOrganizationProBundleInstaller.php (lines 48-64)
    • Column addition with extended entity metadata
    • This runs too late in the initialization sequence

Triggered By:

  • vendor/oro/platform/src/Oro/Bundle/EntityConfigBundle/Migration/WarmUpEntityConfigCacheMigration.php
  • This migration executes as part of the oro:migration:load --force command
  • It runs the oro:entity-config:cache:warmup command within a migration context
  • Failure here blocks the entire migration sequence

Recommended Solutions

Short-term (Defensive)

Add exception handling in classes that query is_global field:

OrganizationProHelper.php:

public function getGlobalOrganization(): ?Organization
{
    if (false === $this->globalOrganization) {
        try {
            $this->globalOrganization = $this->doctrine->getRepository(Organization::class)
                ->createQueryBuilder('org')
                ->select('org')
                ->where('org.is_global = :isGlobal')
                ->setParameter('isGlobal', true)
                ->getQuery()
                ->getOneOrNullResult();
        } catch (QueryException $e) {
            // During initialization, is_global metadata may not yet be synchronized
            // Fall back to first enabled organization
            $this->globalOrganization = $this->doctrine->getRepository(Organization::class)
                ->createQueryBuilder('org')
                ->select('org')
                ->where('org.enabled = :enabled')
                ->setParameter('enabled', true)
                ->orderBy('org.id', 'ASC')
                ->setMaxResults(1)
                ->getQuery()
                ->getOneOrNullResult() ?? null;
        }
    }

    return $this->globalOrganization;
}

OwnershipProConditionDataBuilder.php:

private function getGlobalOrganizationId(?Organization $tokenOrganization): ?int
{
    if (!$this->globalOrganizationId) {
        try {
            if ($tokenOrganization && $tokenOrganization->getIsGlobal()) {
                $this->globalOrganizationId = $tokenOrganization->getid();
            } else {
                $globalOrganization = $this->doctrine->getRepository(Organization::class)
                    ->findOneBy(['is_global' => true]);

                if ($globalOrganization instanceof Organization) {
                    $this->globalOrganizationId = $globalOrganization->getid();
                }
            }
        } catch (\Throwable $e) {
            // During initialization, is_global metadata may not yet be synchronized
            // Return null and try again later
            return null;
        }
    }

    return $this->globalOrganizationId;
}

Additional information

Similar Root Causes

This is a well-known pattern in ORM frameworks when dealing with dynamic/extended entity metadata:

  • Entity schema updates ≠ Entity metadata updates
  • Schema changes don't automatically invalidate and refresh ORM metadata caches
  • Initialization order matters significantly

Impact

  • Fresh Installations: Completely blocked - cannot complete installation
  • Upgrade Installations: May be affected if migrations bring in OrganizationProBundle features
  • Environment: Affects all PHP versions, but most visible on PHP 8.4+ with strict type checking

Workaround

For now, users can:

  1. Skip the affected bundles during initial migrations
  2. Run two-step installation (migrations, then warmup separately)
  3. Apply defensive exception handling in application code

Verification Commands

Before fix (will fail during migrations):

# This will fail during the WarmUpEntityConfigCacheMigration step
bin/console oro:migration:load --force 2>&1 | grep -A 10 "is_global"

Direct warmup (also fails during initialization):

# This will fail if entity metadata not yet synchronized
bin/console oro:entity-config:cache:warmup -vvv 2>&1 | grep -A 10 "is_global"

After fix:

# Both should complete successfully
bin/console oro:migration:load --force
bin/console oro:entity-config:cache:warmup
bin/console oro:platform:update --force

Related Issues

This issue is related to the broader extended entity initialization sequence. Similar problems may exist with:

  • Other extended fields accessed during initialization
  • Custom extended entities added by bundles
  • Entity config loading before extended metadata is ready

Environment Notes

  • Oro Platform 6.1.0 is on PHP 8.4.14+

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