
Oro Entity
- 5 installs
- 2 repo stars
- Updated July 22, 2026
- netresearch/orocommerce-skill
Helps with ai & agent building tasks.
About
oro-entity is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- oro-entity
- AI & Agent Building
- AI-coding skill
Oro Entity by the numbers
- 5 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netresearch/orocommerce-skill --skill oro-entityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 2 |
| Last updated | July 22, 2026 |
| Repository | netresearch/orocommerce-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
OroCommerce v6.1 Entity Development
Canonical Entity (PHP 8 Attributes)
This is the reference pattern combining ExtendEntityInterface, ownership, security, and #[ConfigField]:
<?php
namespace Acme\Bundle\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Oro\Bundle\EntityConfigBundle\Metadata\Attribute\Config;
use Oro\Bundle\EntityConfigBundle\Metadata\Attribute\ConfigField;
use Oro\Bundle\EntityExtendBundle\Entity\ExtendEntityInterface;
use Oro\Bundle\EntityExtendBundle\Entity\ExtendEntityTrait;
use Oro\Bundle\OrganizationBundle\Entity\Organization;
use Oro\Bundle\UserBundle\Entity\User;
#[ORM\Entity]
#[ORM\Table(name: 'acme_demo_document')]
#[Config(
routeName: 'acme_demo_document_index',
routeView: 'acme_demo_document_view',
defaultValues: [
'entity' => ['icon' => 'fa-file', 'label' => 'Document', 'plural_label' => 'Documents'],
'ownership' => [
'owner_type' => 'USER',
'owner_field_name' => 'owner',
'owner_column_name' => 'user_owner_id',
'organization_field_name' => 'organization',
'organization_column_name' => 'organization_id',
],
'security' => ['type' => 'ACL', 'permissions' => 'VIEW;CREATE;EDIT;DELETE', 'group_name' => ''],
]
)]
class Document implements ExtendEntityInterface
{
use ExtendEntityTrait;
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
#[ConfigField(defaultValues: ['dataaudit' => ['auditable' => true]])]
private string $title;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'user_owner_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
private ?User $owner = null;
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(name: 'organization_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
private ?Organization $organization = null;
// Getters/setters...
}ExtendEntityInterface
Use ExtendEntityInterface + ExtendEntityTrait only when admin users need to add custom fields at runtime via Entity Management (System -> Entity Management). The trait provides magic __get/__set/__isset/__call for runtime-added fields. Standard entities that won't be extended at runtime don't need it.
Ownership Decision Tree
- Static/reference data? -> GLOBAL
- Shared within org, not by department? -> ORGANIZATION
- Department/team owned? -> BUSINESS_UNIT (include both org + BU fields)
- Personal/assigned to user? -> USER (include both org + user fields)
CRITICAL: USER and BUSINESS_UNIT ownership both require an `organization` field. Missing it causes silent access control failures. See references/ownership-types.md for full config of all four types.
Migration: Creating a Table
namespace Acme\Bundle\DemoBundle\Migrations\Schema\v1_0;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
class CreateDocumentTable implements Migration
{
public function up(Schema $schema, QueryBag $queries): void
{
$table = $schema->createTable('acme_demo_document');
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('subject', 'string', ['length' => 255]);
$table->addColumn('organization_id', 'integer', ['notnull' => false]);
$table->addColumn('owner_id', 'integer', ['notnull' => false]);
$table->setPrimaryKey(['id']);
}
}Place migrations in src/Acme/Bundle/DemoBundle/Migrations/Schema/. Organize by version subdirectories (v1_0/, v1_1/).
Key Pitfalls
1. Missing organization field on USER/BUSINESS_UNIT ownership — Access control fails silently 2. Using old `@ORM\` annotations instead of `#[ORM\...]` attributes — Doctrine won't recognize them in v6.1 3. Enum codes over 21 characters — Oro uses them to generate table names; exceeding the limit causes silent failures
See Also
references/ownership-types.md— Complete ownership type configurations and field requirementsreferences/entity-patterns.md— Enum entities, ConfigField details, extending core entities, repositories, commands, additional pitfallsreferences/v6.1.md— v6.1 specifics, migration checklist, and common failuresreferences/v7.0.md— v7.0 changes (placeholder)
Entity Patterns Reference
Enum Entities (Option Sets)
Enums in Oro are managed option sets (select or multiselect fields) created via ExtendExtension in migrations. They are not standalone entities you define in PHP — instead, you add an enum field to an existing table and Oro generates the backing entity automatically.
Creating an Enum Field via Migration
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareTrait;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
class AddDocumentStatusEnum implements Migration, ExtendExtensionAwareInterface
{
use ExtendExtensionAwareTrait;
#[\Override]
public function up(Schema $schema, QueryBag $queries): void
{
$this->extendExtension->addEnumField(
$schema,
'acme_demo_document', // table name
'status', // field name
'document_status', // enum code (unique identifier)
false, // multiple: false = select, true = multiselect
false, // immutable
[
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
]
);
}
}Loading Enum Options
Retrieve available enum choices programmatically:
$this->container->get('oro_entity_extend.enum_value_provider')
->getEnumChoices('document_status');Constraints
- Enum codes must be globally unique across the entire application
- Max 21 characters for the enum code — Oro uses it to generate table names, and exceeding this limit causes silent failures
ConfigField Attribute (Detailed)
Use #[ConfigField] to customize field behavior in the UI:
#[ORM\Column(name: 'email', type: 'string', length: 255)]
#[ConfigField(
defaultValues: [
'dataaudit' => [
'auditable' => true,
],
'importexport' => [
'order' => 10,
'identity' => true,
],
]
)]
private $email;Common options:
auditable: true— Track changes in audit logidentity: true— Use for import/export identity matchingorder: N— Column order in import/export
Extending Core Entities via Migration
Extend core entities (Product, Order, Customer) using the ExtendExtensionAwareInterface:
<?php
namespace Acme\Bundle\DemoBundle\Migrations\Schema;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionInterface;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
class AddCustomFieldToProduct implements Migration, ExtendExtensionAwareInterface
{
private ExtendExtensionInterface $extendExtension;
#[\Override]
public function setExtendExtension(ExtendExtensionInterface $extendExtension): void
{
$this->extendExtension = $extendExtension;
}
public function up(Schema $schema, QueryBag $queries): void
{
$this->extendExtension->addColumn(
$schema,
'oro_product',
'custom_string_field',
'string',
[
'extend' => [
'is_extend' => true,
'owner' => 'custom', // Prefer ExtendScope::OWNER_CUSTOM constant
],
'datagrid' => [
'is_visible' => true,
],
]
);
$this->extendExtension->addColumn(
$schema,
'oro_product',
'custom_option_field',
'string',
[
'extend' => [
'is_extend' => true,
'owner' => 'custom', // Prefer ExtendScope::OWNER_CUSTOM constant
'target_entity' => 'Oro\Bundle\EntityExtendBundle\Entity\EnumValue',
'target_field' => 'id',
],
]
);
}
}The addColumn() method handles the oro_*_table naming convention automatically.
Entity Repository
Create a repository class for complex queries:
<?php
namespace Acme\Bundle\DemoBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
use Acme\Bundle\DemoBundle\Entity\Document;
class DocumentRepository extends EntityRepository
{
public function findByOrganization($organizationId)
{
return $this->createQueryBuilder('d')
->where('d.organization = :org')
->setParameter('org', $organizationId)
->orderBy('d.createdAt', 'DESC')
->getQuery()
->getResult();
}
}Attach to entity via #[ORM\Entity(repositoryClass: DocumentRepository::class)].
Migration Versioning
Organize migrations by version:
Migrations/Schema/
├── v1_0/
│ ├── CreateDocumentTable.php
│ └── AddIndexes.php
├── v1_1/
│ ├── AddCustomFieldToDocument.php
├── v1_2/
│ └── ...Version subdirectories help track schema evolution. Use consistent naming: CreateTableName.php or AddFieldToTable.php.
Essential Commands
After entity/migration changes, run:
# Clear extended entity cache (required for ConfigField/Config attribute changes)
php bin/console oro:entity-extend:cache:clear
# Update database schema
php bin/console oro:entity-extend:update-schema
# Run migrations
php bin/console doctrine:migrations:migrate
# Verify entities loaded correctly
php bin/console doctrine:mapping:infoWhy this order matters: Extend cache must clear before schema updates; migrations apply actual DDL.
Additional Pitfalls
- Misusing `#[\Override]` — Use
#[\Override]only on methods that genuinely implement an interface or override a parent class method (e.g.,setExtendExtensionfromExtendExtensionAwareInterface). Do NOT add it to custom repository methods likefindByOrganization()— PHP 8.3+ will throw a fatal error - Creating fields with nullable=true when ownership requires them — Database constraints fail on insert
- Not running `oro:entity-extend:cache:clear` — ConfigField changes won't appear in the UI
OroCommerce Entity Ownership Types — Complete Reference
Quick Decision Matrix
| Ownership Type | Access Scope | Multi-Org | Multi-User | Example | Organization Field | Owner Field |
|---|---|---|---|---|---|---|
| GLOBAL | System-wide | No | No | Settings, product taxonomies | No | No |
| ORGANIZATION | Single organization | Yes | Shared | Shared catalogs, contracts | Required | No |
| BUSINESS_UNIT | Department/team | Yes | Shared by BU | Sales queue, support tickets | Required | No* |
| USER | Personal/assigned | Yes | One per record | Tasks, notes, draft orders | Required | Required |
*BUSINESS_UNIT can have an optional owner, but organization is mandatory.
---
Ownership Type Details
GLOBAL
System-wide scope. No ownership tracking.
When to use:
- Reference data (product types, statuses, enumerations)
- System settings and configuration
- Global master data (currencies, units of measure)
- Shared taxonomies across all organizations
Configuration:
#[Config(
defaultValues: [
'ownership' => [
'owner_type' => 'GLOBAL',
],
]
)]
class ProductType
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string')]
private $name;
}Database schema:
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('name', 'string', ['length' => 255]);
// No organization_id, no owner_idAccess control:
- All users in all organizations can read
- Only admin users can modify
- No row-level filtering
Queries:
// Simple, no filtering needed
$products = $this->entityManager
->getRepository(ProductType::class)
->findAll();---
ORGANIZATION
Multi-organization scope. Shared within an organization but isolated between them.
When to use:
- Shared resources across departments within an organization
- Organization-level catalogs or configurations
- Shared documents, templates, or policies
- Contracts, agreements that apply org-wide
Configuration:
use Oro\Bundle\OrganizationBundle\Entity\Organization;
#[ORM\Entity]
#[ORM\Table(name: 'acme_demo_catalog')]
#[Config(
defaultValues: [
'ownership' => [
'owner_type' => 'ORGANIZATION',
'owner_field_name' => 'organization',
'owner_column_name' => 'organization_id',
],
]
)]
class Catalog
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string')]
private $name;
// MANDATORY field
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(name: 'organization_id', nullable: false)]
private $organization;
public function setOrganization(Organization $organization): void
{
$this->organization = $organization;
}
}Database schema:
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('name', 'string', ['length' => 255]);
$table->addColumn('organization_id', 'integer', ['notnull' => false]);
$table->addIndex(['organization_id'], 'idx_org');Access control:
- Users see only catalogs in their organization
- Organization admins can modify catalogs
- Row filtering by organization applied automatically
Queries:
// Oro's ACL automatically filters by current organization
$catalogs = $this->entityManager
->getRepository(Catalog::class)
->findAll(); // Only current org's catalogs returned
// Explicit filtering (rare)
$catalogs = $this->entityManager
->getRepository(Catalog::class)
->findBy(['organization' => $organizationId]);---
BUSINESS_UNIT
Department/team scope. Implies multi-organization.
When to use:
- Department-owned resources (sales queue, support tickets)
- Team-specific workflows
- Hierarchical access (parent BU inherits child BU records)
- Role-based resource grouping
Configuration:
use Oro\Bundle\OrganizationBundle\Entity\Organization;
use Oro\Bundle\OrganizationBundle\Entity\BusinessUnit;
#[ORM\Entity]
#[ORM\Table(name: 'acme_demo_ticket')]
#[Config(
defaultValues: [
'ownership' => [
'owner_type' => 'BUSINESS_UNIT',
'owner_field_name' => 'businessUnit',
'owner_column_name' => 'business_unit_id',
],
]
)]
class SupportTicket
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string')]
private $subject;
// MANDATORY: Organization field
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(name: 'organization_id', nullable: false)]
private $organization;
// MANDATORY: Business Unit field (specifies the owner department)
#[ORM\ManyToOne(targetEntity: BusinessUnit::class)]
#[ORM\JoinColumn(name: 'business_unit_id', nullable: false)]
private $businessUnit;
public function setOrganization(Organization $organization): void
{
$this->organization = $organization;
}
public function setBusinessUnit(BusinessUnit $businessUnit): void
{
$this->businessUnit = $businessUnit;
}
}Database schema:
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('subject', 'string', ['length' => 255]);
$table->addColumn('organization_id', 'integer', ['notnull' => false]);
$table->addColumn('business_unit_id', 'integer', ['notnull' => false]);
$table->addIndex(['organization_id'], 'idx_ticket_org');
$table->addIndex(['business_unit_id'], 'idx_ticket_bu');Access control:
- Users see tickets assigned to their business unit
- Parent business units can see child business unit tickets (hierarchical)
- Business unit heads/admins can reassign between BUs
- Row filtering by BU applied automatically
Queries:
// ACL filters by current user's business unit (and parents)
$tickets = $this->entityManager
->getRepository(SupportTicket::class)
->findAll(); // Only current BU + parent BUs' tickets returned
// Explicit filtering (for override)
$tickets = $this->entityManager
->getRepository(SupportTicket::class)
->findBy(['businessUnit' => $businessUnitId]);---
USER
Personal/individual ownership. Implies multi-organization.
When to use:
- Personal tasks and reminders
- Draft/unpublished work (draft orders, draft documents)
- Individual assignments (support tickets assigned to a person)
- User-specific preferences or settings
Configuration:
use Oro\Bundle\OrganizationBundle\Entity\Organization;
use Oro\Bundle\UserBundle\Entity\User;
#[ORM\Entity]
#[ORM\Table(name: 'acme_demo_task')]
#[Config(
defaultValues: [
'ownership' => [
'owner_type' => 'USER',
'owner_field_name' => 'owner',
'owner_column_name' => 'owner_id',
],
]
)]
class Task
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string')]
private $title;
#[ORM\Column(type: 'text', nullable: true)]
private $description;
// MANDATORY: Organization field (user must be in this org)
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(name: 'organization_id', nullable: false)]
private $organization;
// MANDATORY: Owner field (the user who owns the task)
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'owner_id', nullable: false)]
private $owner;
public function setOrganization(Organization $organization): void
{
$this->organization = $organization;
}
public function setOwner(User $owner): void
{
$this->owner = $owner;
}
}Database schema:
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('title', 'string', ['length' => 255]);
$table->addColumn('description', 'text', ['notnull' => false]);
$table->addColumn('organization_id', 'integer', ['notnull' => false]);
$table->addColumn('owner_id', 'integer', ['notnull' => false]);
$table->addIndex(['organization_id'], 'idx_task_org');
$table->addIndex(['owner_id'], 'idx_task_owner');Access control:
- Users see only their own tasks
- Managers with appropriate permission can see team member tasks
- Row filtering by owner applied automatically
- Owner can delete or transfer task
Queries:
// ACL filters by current user (or team if manager has permission)
$myTasks = $this->entityManager
->getRepository(Task::class)
->findAll(); // Only current user's tasks returned
// Explicit filtering (for override)
$tasks = $this->entityManager
->getRepository(Task::class)
->findBy(['owner' => $userId]);
// Find by current user
$currentUser = $this->tokenStorage->getToken()->getUser();
$myTasks = $this->entityManager
->getRepository(Task::class)
->findBy(['owner' => $currentUser]);---
Hybrid Pattern: USER with Optional BUSINESS_UNIT
Some entities support both USER ownership with an optional BUSINESS_UNIT context:
#[ORM\Entity]
#[Config(
defaultValues: [
'ownership' => [
'owner_type' => 'USER',
'owner_field_name' => 'owner',
'owner_column_name' => 'owner_id',
],
]
)]
class DraftOrder
{
#[ORM\Column(type: 'integer')]
private $id;
// MANDATORY
#[ORM\ManyToOne(targetEntity: Organization::class)]
#[ORM\JoinColumn(name: 'organization_id', nullable: false)]
private $organization;
// MANDATORY
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'owner_id', nullable: false)]
private $owner;
// OPTIONAL: For tracking which department created the draft
#[ORM\ManyToOne(targetEntity: BusinessUnit::class)]
#[ORM\JoinColumn(name: 'business_unit_id', nullable: true)]
private $businessUnit;
}Use this pattern when:
- Record is personally owned but linked to a department for audit/tracking
- Department managers need visibility into team member's drafts
---
Migration Example: Adding Ownership to Existing Entity
<?php
namespace Acme\Bundle\DemoBundle\Migrations\Schema;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
class AddOwnershipToDocument implements Migration
{
public function up(Schema $schema, QueryBag $queries): void
{
$table = $schema->getTable('acme_demo_document');
// Add organization column
$table->addColumn(
'organization_id',
'integer',
['notnull' => false]
);
// Add owner column
$table->addColumn(
'owner_id',
'integer',
['notnull' => false]
);
// Add indexes for performance
$table->addIndex(['organization_id'], 'idx_document_org');
$table->addIndex(['owner_id'], 'idx_document_owner');
// Foreign key constraints (optional but recommended)
$table->addForeignKeyConstraint(
$schema->getTable('oro_organization'),
['organization_id'],
['id'],
['onDelete' => 'SET NULL']
);
$table->addForeignKeyConstraint(
$schema->getTable('oro_user'),
['owner_id'],
['id'],
['onDelete' => 'SET NULL']
);
}
}Then backfill existing records:
// In a separate data migration (v1_1 directory)
public function up(Schema $schema, QueryBag $queries): void
{
// Assign all existing documents to the default org and a default user
$queries->addQuery(
'UPDATE acme_demo_document SET organization_id = 1, owner_id = 1'
);
}---
Testing Ownership Logic
class DocumentRepositoryTest extends TestCase
{
public function testFindByOrganization()
{
$org1 = $this->createOrganization('Org 1');
$org2 = $this->createOrganization('Org 2');
$doc1 = new Document();
$doc1->setOrganization($org1);
$doc1->setOwner($this->getTestUser());
$this->em->persist($doc1);
$doc2 = new Document();
$doc2->setOrganization($org2);
$doc2->setOwner($this->getTestUser());
$this->em->persist($doc2);
$this->em->flush();
// Verify organization filter works
$this->em->getUnitOfWork()->clear();
// Set current organization context to org1
// Query should return only doc1
}
}---
Common Mistakes
1. Missing organization field on USER/BUSINESS_UNIT entities
- Results in
NOT NULL constraint failedon insert - Always include organization
2. Using nullable=true on ownership fields
- Violates data integrity
- Keep ownership fields
nullable: false
3. Forgetting to set ownership fields when creating records
- Records become orphaned
- Set organization/owner in controller/service constructor
4. Assigning wrong ownership type to entity
- If you need department scoping, use BUSINESS_UNIT, not USER
- If you need personal ownership, use USER, not ORGANIZATION
- Wrong type = access control failures
5. Not updating #[Config] attribute after database changes
- Entity cache won't reflect new ownership configuration
- Always run
oro:entity-extend:cache:clearafter changes
Entity Development — v6.1 Notes
Key Environment
- PHP 8.1+ required; 8.2+ recommended
- Doctrine 2.13+ (full attribute support)
- PostgreSQL primary; MySQL in legacy mode
Changes from Earlier Versions
- PHP 8 attributes are mandatory; Doctrine 2.13+ ignores docblock annotations entirely
ExtendEntityInterfaceis the standard pattern for custom-field-capable entities- Migrations use
ExtendExtensionInterfacefor core entity extension
Important Constraints
- Attribute parsing is done at runtime, not compile-time
- Large entity sets (100+ entities) may incur slight startup overhead — use
oro:cachewarmup in production - Ownership fields must be real Doctrine columns, not derived properties
- Custom fields from ExtendEntity are stored in separate tables (auto-managed by Oro); queries on them may require joins
Common v6.1 Failures
1. Mixing attributes and annotations — Doctrine reads only attributes; annotations are silently ignored 2. Missing organization on USER/BUSINESS_UNIT entities — access control silently fails 3. Not implementing ExtendEntityInterface — custom fields won't appear in Entity Management UI 4. Using generic `addColumn()` instead of `ExtendExtensionInterface` — bypasses Oro's extended entity system 5. Forgetting `oro:entity-extend:cache:clear` — ConfigField changes invisible until cache clears
Migration Checklist: v6.0 to v6.1
- [ ] Replace all docblock
@ORM\with#[ORM\...]attributes - [ ] Verify all entities use ConfigField for custom-field support where needed
- [ ] Review ownership configuration; add organization fields if missing
- [ ] Test migrations with PostgreSQL
- [ ] Run
oro:entity-extend:cache:clearafter entity changes - [ ] Verify
doctrine:mapping:infolists all entities correctly
Performance Notes
- Keep migration files focused (one feature per file)
- Use version subdirectories to group related schema changes
- Test migrations with
doctrine:migrations:execute --dry-runbefore applying - Monitor query performance when extending heavily-used entities (Product, Order)
Entity Development — v7.0 Notes
v7.0 is not yet released. This file will be updated when v7.0 stabilizes.
Expected Changes
- TBD