feat: initial commit — BMAD tooling, Claude memories, firmware scaffold
Adds the complete project foundation: - BMAD BMM workflow tooling (_bmad/) - Claude slash commands, skills, and project memories (.claude/) - ESP32 firmware scaffold (PlatformIO + Waveshare e-ink driver) - .gitignore excluding _bmad-output/ and .pio/ build artifacts Planning artifacts (PRD, architecture, epics) are intentionally not tracked — they live in _bmad-output/ per project convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
---
|
||||
name: oro-backend-dev
|
||||
description: OroCommerce backend development reference covering entities, CRUD, datagrids, message queue, bundles, security, system configuration, workflows, cron, and integrations. Use when writing PHP code for Oro bundles, creating entities, building controllers, configuring datagrids, or implementing backend business logic.
|
||||
---
|
||||
|
||||
# OroCommerce Backend Developer
|
||||
|
||||
Reference for backend PHP development on OroCommerce/OroPlatform.
|
||||
Full docs: https://doc.oroinc.com/backend/
|
||||
|
||||
## Bundle Structure
|
||||
|
||||
All custom code lives in bundles under `src/Acme/Bundle/{Name}Bundle/`.
|
||||
Bundle priority > 210 to load after Oro core bundles.
|
||||
|
||||
```
|
||||
AcmeExampleBundle/
|
||||
AcmeExampleBundle.php # extends Bundle
|
||||
DependencyInjection/
|
||||
AcmeExampleExtension.php # loads services.yml
|
||||
Configuration.php # config tree (if needed)
|
||||
Resources/
|
||||
config/
|
||||
oro/
|
||||
bundles.yml # { name: FQCN, priority: 255 }
|
||||
datagrids.yml
|
||||
navigation.yml
|
||||
workflows.yml
|
||||
features.yml
|
||||
actions.yml # operations/action buttons
|
||||
processes.yml # entity lifecycle automation
|
||||
search.yml # search index config
|
||||
placeholders.yml # back-office UI injection
|
||||
dashboards.yml # dashboard widgets
|
||||
api.yml # API entity config
|
||||
acls.yml # YAML-based ACL definitions
|
||||
services.yml
|
||||
translations/messages.en.yml
|
||||
Entity/
|
||||
Controller/
|
||||
Form/Type/
|
||||
EventListener/
|
||||
Migrations/Schema/
|
||||
Tests/Unit/
|
||||
```
|
||||
|
||||
Auto-registration via `Resources/config/oro/bundles.yml`:
|
||||
|
||||
```yaml
|
||||
bundles:
|
||||
- { name: Acme\Bundle\ExampleBundle\AcmeExampleBundle, priority: 255 }
|
||||
```
|
||||
|
||||
## Entities
|
||||
|
||||
Use PHP 8 ORM attributes. Oro entities need `#[Config]` for ACL, routing, and extend support.
|
||||
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'acme_example')]
|
||||
#[Config(
|
||||
routeName: 'acme_example_index',
|
||||
routeView: 'acme_example_view',
|
||||
defaultValues: ['entity' => ['icon' => 'fa-cube']]
|
||||
)]
|
||||
class Example implements ExtendEntityInterface
|
||||
{
|
||||
use ExtendEntityTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
}
|
||||
```
|
||||
|
||||
### #[ConfigField] Attribute
|
||||
|
||||
Use `#[ConfigField]` on individual properties for field-level config:
|
||||
|
||||
```php
|
||||
#[ORM\Column(name: 'subject', type: 'string', length: 255)]
|
||||
#[ConfigField(defaultValues: [
|
||||
'dataaudit' => ['auditable' => true],
|
||||
'importexport' => ['identity' => true],
|
||||
])]
|
||||
private $subject;
|
||||
```
|
||||
|
||||
Config commands: `oro:entity-config:update`, `oro:entity-config:cache:clear`,
|
||||
`oro:entity-config:debug "FQCN"`.
|
||||
|
||||
### Extend Existing Oro Entities
|
||||
|
||||
Add fields to Oro entities via migrations with `oro_options`:
|
||||
|
||||
```php
|
||||
$table = $schema->getTable('oro_order');
|
||||
$table->addColumn('custom_field', 'string', [
|
||||
'oro_options' => [
|
||||
'extend' => ['is_extend' => true, 'owner' => ExtendScope::OWNER_CUSTOM],
|
||||
'entity' => ['label' => 'Custom Field'],
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
Never modify Oro entity classes directly. Clear extend cache after:
|
||||
`php bin/console oro:entity-extend:cache:clear`
|
||||
|
||||
## Migrations
|
||||
|
||||
Implement `Oro\Bundle\MigrationBundle\Migration\Migration` (incremental) or
|
||||
`Installation` (full schema). Place in `Migrations/Schema/v{N}/`.
|
||||
|
||||
Run: `php bin/console oro:migration:load`
|
||||
|
||||
## Datagrids
|
||||
|
||||
Configure in `Resources/config/oro/datagrids.yml`. Key sections:
|
||||
- `source`: ORM query (select, from, join); protect with `acl_resource`
|
||||
- `columns`: display config with labels and `frontend_type`
|
||||
- `sorters`: sortable columns with `data_name`
|
||||
- `filters`: filter widgets (string, datetime, entity, boolean)
|
||||
- `properties` + `actions`: row links (view, edit, delete); hide with `acl_resource`
|
||||
|
||||
Set `extended_entity_name` if entity supports dynamic fields.
|
||||
|
||||
### Customizing Existing Datagrids
|
||||
|
||||
Two approaches:
|
||||
1. **YAML override**: add columns/sorters/filters in your bundle's `datagrids.yml`
|
||||
(Oro merges configs from all bundles by priority).
|
||||
2. **Event listener**: listen to `oro_datagrid.datagrid.build.before.{grid-name}`
|
||||
and `oro_datagrid.orm_datasource.result.after.{grid-name}` for complex
|
||||
modifications (joins, computed columns, post-fetch decoration).
|
||||
|
||||
Custom filter types: implement filter class, register with
|
||||
`oro_filter.extension.orm_filter.filter` tag.
|
||||
|
||||
## CRUD Controllers
|
||||
|
||||
Pattern: FormType + Controller + Twig templates.
|
||||
|
||||
- Controller uses `#[Acl]` attributes for permissions
|
||||
- `UpdateHandlerFacade` handles form submission
|
||||
- Templates extend `@OroUI/actions/{index,view,update}.html.twig`
|
||||
- Routing in `Resources/config/oro/routing.yml`
|
||||
|
||||
## Doctrine Entity Listeners
|
||||
|
||||
For entity-specific event hooks (postPersist, preUpdate, postUpdate), use Doctrine entity listeners
|
||||
instead of generic event subscribers. Register via service tags:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
acme.entity_listener.customer_sync:
|
||||
class: Acme\Bundle\EclipseBundle\EventListener\CustomerSync
|
||||
arguments: ['@oro_message_queue.client.message_producer', '@logger']
|
||||
tags:
|
||||
- { name: doctrine.orm.entity_listener, entity_manager: default, entity: Oro\Bundle\CustomerBundle\Entity\Customer, event: preUpdate }
|
||||
- { name: doctrine.orm.entity_listener, entity_manager: default, entity: Oro\Bundle\CustomerBundle\Entity\Customer, event: postUpdate }
|
||||
- { name: oro_featuretoggle.feature, feature: acme_eclipse_sync }
|
||||
calls:
|
||||
- [ addFieldToListenChanges, [ 'name' ] ]
|
||||
- [ addFieldToListenChanges, [ 'email' ] ]
|
||||
```
|
||||
|
||||
Abstract base pattern for field-level change tracking:
|
||||
|
||||
```php
|
||||
abstract class AbstractEntityModificationListener
|
||||
{
|
||||
protected array $entitiesIdToSync = [];
|
||||
protected array $fieldsToListenChanges = [];
|
||||
|
||||
public function __construct(
|
||||
protected MessageProducerInterface $messageProducer,
|
||||
protected LoggerInterface $logger
|
||||
) {}
|
||||
|
||||
public function addFieldToListenChanges(string $value): void
|
||||
{
|
||||
if (!in_array($value, $this->fieldsToListenChanges)) {
|
||||
$this->fieldsToListenChanges[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function preUpdate(object $entity, PreUpdateEventArgs $event): void
|
||||
{
|
||||
foreach ($this->fieldsToListenChanges as $fieldName) {
|
||||
if ($event->hasChangedField($fieldName)) {
|
||||
$this->entitiesIdToSync[$entity->getId()] = $entity->getId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Message Queue
|
||||
|
||||
Processors implement `MessageProcessorInterface` + `TopicSubscriberInterface`.
|
||||
Return `self::ACK`, `self::REJECT`, or `self::REQUEUE`.
|
||||
Tag service: `oro_message_queue.client.message_processor`.
|
||||
Use `JobRunner::createUnique()` for non-duplicate jobs.
|
||||
|
||||
Transport: DBAL (default, polls DB) or RabbitMQ (EE, via `ORO_MQ_DSN`).
|
||||
|
||||
### Topic Registration Shorthand
|
||||
|
||||
Use `_defaults` in `mq_topics.yml` to auto-tag all topic classes:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
_defaults:
|
||||
tags:
|
||||
- { name: oro_message_queue.topic }
|
||||
|
||||
Acme\Bundle\EclipseBundle\Async\Topic\SyncOrderStatusTopic: ~
|
||||
Acme\Bundle\EclipseBundle\Async\Topic\SyncOriginOrdersTopic: ~
|
||||
Acme\Bundle\CpnBundle\Async\Topic\SyncCpnFromEclipseTopic: ~
|
||||
```
|
||||
|
||||
### Processor Auto-Tagging via TopicSubscriberInterface
|
||||
|
||||
Alternative to specifying `topicName` in the service tag -- implement `TopicSubscriberInterface`
|
||||
and use `_instanceof` in `mq_processors.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
_instanceof:
|
||||
Oro\Component\MessageQueue\Client\TopicSubscriberInterface:
|
||||
tags:
|
||||
- { name: oro_message_queue.client.message_processor }
|
||||
|
||||
acme.eclipse.processor.sync_order_status:
|
||||
class: Acme\Bundle\EclipseBundle\Async\SyncOrderStatusProcessor
|
||||
parent: acme.async.abstract_processor
|
||||
```
|
||||
|
||||
The processor class must implement `getSubscribedTopics()` returning the topic name.
|
||||
|
||||
### Abstract Processor Pattern
|
||||
|
||||
For processors sharing common patterns (job uniqueness, redelivery limits, feature toggle):
|
||||
|
||||
```php
|
||||
abstract class AbstractAcmeProcessor implements MessageProcessorInterface, TopicSubscriberInterface
|
||||
{
|
||||
use FeatureCheckerHolderTrait;
|
||||
public const MAX_REDELIVERY_COUNT = 5;
|
||||
|
||||
public function __construct(
|
||||
protected JobRunner $jobRunner,
|
||||
protected ManagerRegistry $registry,
|
||||
protected LoggerInterface $logger
|
||||
) {}
|
||||
|
||||
public function process(MessageInterface $message, SessionInterface $session): string
|
||||
{
|
||||
if (!$this->isFeaturesEnabled()) {
|
||||
return self::REJECT;
|
||||
}
|
||||
|
||||
$redeliveryCount = $message->getProperty(RedeliveryMessageExtension::PROPERTY_REDELIVER_COUNT);
|
||||
if ($redeliveryCount !== null && $redeliveryCount >= static::MAX_REDELIVERY_COUNT) {
|
||||
$this->logger->critical('Redelivery limit reached', ['message' => $message]);
|
||||
return self::REJECT;
|
||||
}
|
||||
|
||||
$result = $this->jobRunner->runUnique(
|
||||
$message->getMessageId(),
|
||||
$this->getJobName($message->getBody()),
|
||||
fn() => $this->processMessage($message->getBody())
|
||||
);
|
||||
|
||||
return $result ? self::ACK : self::REJECT;
|
||||
}
|
||||
|
||||
abstract protected function processMessage(array $body): bool;
|
||||
abstract protected function getJobName(array $body): string;
|
||||
}
|
||||
```
|
||||
|
||||
## Security & ACL
|
||||
|
||||
Entity ACL via `#[Config(defaultValues: ['security' => [...], 'ownership' => [...]])]`.
|
||||
Controller ACL via `#[Acl(id: '...', type: 'entity', class: '...', permission: 'VIEW')]`.
|
||||
Permissions: VIEW, CREATE, EDIT, DELETE, ASSIGN.
|
||||
|
||||
Also available:
|
||||
- `#[AclAncestor('existing_acl_id')]` to reuse ACL definitions on controllers.
|
||||
- `acls.yml` for YAML-based ACL definitions.
|
||||
- `acl_resource` on navigation items to hide unauthorized menus.
|
||||
- `is_granted('acl_id', entity)` in Twig for conditional rendering.
|
||||
- `AclHelper::apply($queryBuilder)` for ACL-filtered ORM queries.
|
||||
|
||||
## System Configuration
|
||||
|
||||
Define settings via `SettingsBuilder::append()` in `Configuration.php`.
|
||||
UI form in `Resources/config/oro/system_configuration.yml` (groups, fields, tree).
|
||||
Access: `$configManager->get('acme_bundle.setting_name')`.
|
||||
|
||||
## Feature Toggles
|
||||
|
||||
Define in `Resources/config/oro/features.yml` with label, toggle (config key),
|
||||
routes, navigation_items, api_resources, mq_topics.
|
||||
Services implement `FeatureToggleableInterface` + `FeatureCheckerHolderTrait`.
|
||||
Twig: `{% if feature_enabled('feature_name') %}`.
|
||||
|
||||
## Workflows
|
||||
|
||||
YAML-based state machines in `Resources/config/oro/workflows.yml`.
|
||||
Define steps, attributes, transitions, conditions, and actions.
|
||||
Triggers: user-initiated, cron-based, or entity-event-based.
|
||||
Load: `php bin/console oro:workflow:definitions:load`
|
||||
|
||||
## Cron Jobs
|
||||
|
||||
Commands implement `CronCommandScheduleDefinitionInterface`.
|
||||
Namespace: `oro:cron:acme:{name}`.
|
||||
`getDefaultDefinition()` returns crontab string.
|
||||
Load schedule: `php bin/console oro:cron:definitions:load`
|
||||
|
||||
## Navigation
|
||||
|
||||
`Resources/config/oro/navigation.yml` defines menu items and tree placement.
|
||||
Trees: `application_menu` (main bar), `usermenu`, `shortcuts`.
|
||||
|
||||
## Import/Export
|
||||
|
||||
Register processors tagged `oro_importexport.processor` (type: export/import).
|
||||
Provide template fixtures for download-template feature.
|
||||
Buttons: `{% include '@OroImportExport/ImportExport/buttons_from_configuration.html.twig' %}`
|
||||
|
||||
## Logging
|
||||
|
||||
Use Monolog (PSR-3 LoggerInterface). Inject `@logger` via constructor.
|
||||
Use `{placeholders}` in messages, pass variables in context array.
|
||||
Pass exceptions as `['exception' => $e]`. Never log sensitive data.
|
||||
Custom channels: tag with `{ name: monolog.logger, channel: acme_{bundle} }`.
|
||||
|
||||
## Email Templates
|
||||
|
||||
Create Twig templates in `Resources/emails/` with metadata comments
|
||||
(`@entityName`, `@subject`, `@name`, `@isSystem`).
|
||||
Load via fixture extending `AbstractEmailFixture`.
|
||||
Send via `EmailModelSender::send()`.
|
||||
|
||||
## Commerce Extensions
|
||||
|
||||
- **Payment methods**: Settings entity + Transport + PaymentMethod + Provider + ViewProvider
|
||||
(https://doc.oroinc.com/backend/extend-commerce/payment/)
|
||||
- **Shipping methods**: Settings entity + Transport + ShippingMethod + Types + Provider
|
||||
(https://doc.oroinc.com/backend/extend-commerce/shipping/)
|
||||
- **Checkout**: extend or override the `b2b_flow_checkout` workflow
|
||||
(https://doc.oroinc.com/backend/extend-commerce/checkout-customization-methods/)
|
||||
|
||||
## Testing
|
||||
|
||||
| Level | Base class | Location |
|
||||
|---|---|---|
|
||||
| Unit | PHPUnit TestCase | Tests/Unit/ |
|
||||
| Functional | WebTestCase | Tests/Functional/ |
|
||||
| API | RestJsonApiTestCase | Tests/Functional/Api/ |
|
||||
| BDD | Behat features | Tests/Behat/Features/ |
|
||||
|
||||
## Operations (Actions)
|
||||
|
||||
Define custom operations in `Resources/config/oro/actions.yml` for entity
|
||||
buttons, datagrid mass actions, and route-based actions. Operations support
|
||||
preconditions, conditions, form dialogs, and inline actions.
|
||||
|
||||
Disable default CRUD operations with `exclude_entities`; extend/substitute
|
||||
with `extends` and `substitute_operation`.
|
||||
|
||||
Validate: `php bin/console oro:action:configuration:validate`
|
||||
|
||||
## Scopes
|
||||
|
||||
Scopes provide context-dependent behavior (customer, website, customer group).
|
||||
Use `ScopeManager` to find/create scopes. Implement `ScopeCriteriaProviderInterface`
|
||||
and register with `oro_scope.provider` tag. Apply scope criteria to queries with
|
||||
`ScopeCriteria::applyToJoinWithPriority()`. Used by visibility, pricing, content.
|
||||
|
||||
## Dashboards
|
||||
|
||||
Create widgets in `Resources/config/oro/dashboards.yml`. Widget template extends
|
||||
`@OroDashboard/Dashboard/widget.html.twig`. Route: `oro_dashboard_widget` with
|
||||
`bundle` and `name` parameters (maps to `{Bundle}:Dashboard:{name}`).
|
||||
|
||||
## Data Audit
|
||||
|
||||
Enable change logging via `'dataaudit' => ['auditable' => true]` in `#[Config]`
|
||||
on entity class and `#[ConfigField]` on individual fields.
|
||||
Implements `AuditAdditionalFieldsInterface` for extra fields in audit log.
|
||||
History accessible at `/audit/history/{entity}/{id}` and via REST API.
|
||||
|
||||
## Data Fixtures
|
||||
|
||||
Place in `Migrations/Data/ORM/` (main) or `Migrations/Data/Demo/ORM/` (demo).
|
||||
Implement `FixtureInterface::load(ObjectManager $manager)`.
|
||||
Use `OrderedFixtureInterface` for ordering, `DependentFixtureInterface` for deps.
|
||||
Load: `php bin/console oro:migration:data:load` (add `--fixtures-type=demo`).
|
||||
|
||||
## File Storage
|
||||
|
||||
Oro uses KnpGaufretteBundle for filesystem abstraction (local FS or GridFS).
|
||||
Two adapter types: `public` (direct URL via `/media/`) and `private` (no web access).
|
||||
Register filesystems in `Resources/config/oro/app.yml`, create `FileManager`
|
||||
services as children of `oro_gaufrette.file_manager`.
|
||||
GridFS configured via `oro_gridfs` adapter or `gaufrette_adapter.*` parameters.
|
||||
|
||||
## Reports & Segments
|
||||
|
||||
Reports are datagrid-based. Configure in `datagrids.yml` with `source.acl_resource:
|
||||
oro_report_view`, aggregations, totals, and `options.export: true`.
|
||||
Add to Reports menu via `navigation.yml`.
|
||||
Segments: dynamic (real-time) or static (snapshot in `oro_segment_snapshot`).
|
||||
|
||||
## Search Index
|
||||
|
||||
Configure `Resources/config/oro/search.yml` to make entities searchable.
|
||||
Define alias, fields (target_type: text/integer/double/datetime), relation_fields,
|
||||
route for result links, and search_template for custom rendering.
|
||||
Reindex: `php bin/console oro:search:reindex --class="FQCN"`.
|
||||
|
||||
## Processes
|
||||
|
||||
Automated entity lifecycle tasks in `Resources/config/oro/processes.yml`.
|
||||
Triggers fire on Doctrine events (create/update/delete) or cron schedules.
|
||||
Can run immediately or deferred via MQ with priority and time_shift.
|
||||
Use `exclude_definitions` to prevent recursion.
|
||||
Load: `php bin/console oro:process:configuration:load`.
|
||||
|
||||
## Access Rules
|
||||
|
||||
Custom query-level access restrictions beyond standard ACL.
|
||||
Implement `AccessRuleInterface` (isApplicable + process methods).
|
||||
Register with `oro_security.access_rule` tag (type, entityClass, permission).
|
||||
Modifies ORM queries via `AclHelper::apply()` + `AccessRuleWalker`.
|
||||
|
||||
## Field ACL
|
||||
|
||||
Field-level permissions (VIEW, CREATE, EDIT per field).
|
||||
Enable with `'security' => ['field_acl_supported' => true]` in `#[Config]`.
|
||||
Limit per-field permissions via `#[ConfigField(defaultValues: ['security' => ['permissions' => 'VIEW;CREATE']])]`.
|
||||
Check: `isGranted('VIEW', new FieldVote($entity, 'fieldName'))`.
|
||||
Twig: `{% if is_granted('VIEW', entity, 'fieldName') %}`.
|
||||
|
||||
## Notification Alerts
|
||||
|
||||
Log integration/sync errors visible in admin UI (System > Alerts).
|
||||
Register `NotificationAlertManager` service with source and resource type.
|
||||
Alerts track: source, resource, alert type, operation, step, item ID, external ID.
|
||||
CLI: `oro:notification:alerts:list`, `oro:notification:alerts:cleanup`.
|
||||
Auto-cleanup of resolved alerts older than 30 days via cron.
|
||||
|
||||
## Placeholders
|
||||
|
||||
Inject content into back-office page regions via `Resources/config/oro/placeholders.yml`.
|
||||
Define items with template, order, and optional `applicable` filter.
|
||||
Common targets: `oro_view_additional_data`, `oro_widget_side_bar`.
|
||||
Different from Layout Updates (storefront only).
|
||||
|
||||
## Organization Types (EE)
|
||||
|
||||
Define in `Resources/config/oro/organization_types.yml`. Assign feature
|
||||
restrictions per organization. Strategies: `exclude_list` (default) or
|
||||
`include_list`. Verify: `php bin/console oro:organization-type:config:debug`.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- For detailed entity extension patterns, see [reference-entities.md](reference-entities.md)
|
||||
- For integration patterns, see [reference-integrations.md](reference-integrations.md)
|
||||
- For cloud deployment and operations, see the `oro-cloud` skill
|
||||
@@ -0,0 +1,127 @@
|
||||
# Entity Reference
|
||||
|
||||
Detailed patterns for Oro entity work.
|
||||
|
||||
## Full Entity Example
|
||||
|
||||
```php
|
||||
namespace Acme\Bundle\ExampleBundle\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;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'acme_example')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[Config(
|
||||
routeName: 'acme_example_index',
|
||||
routeView: 'acme_example_view',
|
||||
defaultValues: [
|
||||
'entity' => ['icon' => 'fa-cube'],
|
||||
'security' => ['type' => 'ACL', 'group_name' => ''],
|
||||
'ownership' => [
|
||||
'owner_type' => 'USER',
|
||||
'owner_field_name' => 'owner',
|
||||
'owner_column_name' => 'user_owner_id',
|
||||
'organization_field_name' => 'organization',
|
||||
'organization_column_name' => 'organization_id',
|
||||
],
|
||||
]
|
||||
)]
|
||||
class Example implements ExtendEntityInterface
|
||||
{
|
||||
use ExtendEntityTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255)]
|
||||
#[ConfigField(defaultValues: ['dataaudit' => ['auditable' => true]])]
|
||||
private string $name = '';
|
||||
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private ?\DateTimeInterface $createdAt = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private ?\DateTimeInterface $updatedAt = null;
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function prePersist(): void
|
||||
{
|
||||
$this->createdAt = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
$this->updatedAt = clone $this->createdAt;
|
||||
}
|
||||
|
||||
#[ORM\PreUpdate]
|
||||
public function preUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
// getters/setters...
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Example (Installation)
|
||||
|
||||
```php
|
||||
namespace Acme\Bundle\ExampleBundle\Migrations\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Oro\Bundle\MigrationBundle\Migration\Installation;
|
||||
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
|
||||
|
||||
class AcmeExampleBundleInstaller implements Installation
|
||||
{
|
||||
public function getMigrationVersion(): string
|
||||
{
|
||||
return 'v1_0';
|
||||
}
|
||||
|
||||
public function up(Schema $schema, QueryBag $queries): void
|
||||
{
|
||||
$table = $schema->createTable('acme_example');
|
||||
$table->addColumn('id', 'integer', ['autoincrement' => true]);
|
||||
$table->addColumn('name', 'string', ['length' => 255]);
|
||||
$table->addColumn('created_at', 'datetime');
|
||||
$table->addColumn('updated_at', 'datetime');
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Extend Entity Commands
|
||||
|
||||
```bash
|
||||
php bin/console oro:entity-extend:cache:clear
|
||||
php bin/console oro:entity-extend:cache:warmup
|
||||
php bin/console oro:entity-extend:update-schema
|
||||
php bin/console oro:entity-extend:update-schema --dry-run
|
||||
```
|
||||
|
||||
## Enum/Option Set Fields
|
||||
|
||||
For select/multi-select fields, use the enum pattern:
|
||||
|
||||
```php
|
||||
$table->addColumn('status_id', 'string', [
|
||||
'oro_options' => [
|
||||
'extend' => ['is_extend' => true, 'owner' => ExtendScope::OWNER_SYSTEM],
|
||||
'enum' => ['enum_code' => 'example_status'],
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
## Extended Field Access
|
||||
|
||||
Extended fields are accessed via magic methods generated at cache warmup:
|
||||
|
||||
```php
|
||||
$entity->getMyCustomField();
|
||||
$entity->setMyCustomField($value);
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
# Integration Reference
|
||||
|
||||
Patterns for building integrations in OroCommerce.
|
||||
|
||||
## Oro IntegrationBundle Pattern
|
||||
|
||||
For managed integrations visible in System > Integrations:
|
||||
|
||||
1. **Transport** -- implements `TransportInterface`, handles connectivity
|
||||
2. **Connector** -- implements `ConnectorInterface`, defines sync logic per entity
|
||||
3. **Channel Type** -- defines the integration type shown in UI
|
||||
|
||||
### Transport
|
||||
|
||||
```php
|
||||
class MyTransport implements TransportInterface
|
||||
{
|
||||
public function init(Transport $transportEntity): void { }
|
||||
public function getLabel(): string { return 'My Integration'; }
|
||||
public function getSettingsFormType(): string { return MyTransportSettingsType::class; }
|
||||
public function getSettingsEntityFQCN(): string { return MyTransportSettings::class; }
|
||||
}
|
||||
```
|
||||
|
||||
Tag: `{ name: oro_integration.transport, type: my_integration, channel_type: my_channel }`
|
||||
|
||||
### Connector
|
||||
|
||||
```php
|
||||
class MyConnector implements ConnectorInterface
|
||||
{
|
||||
public function getLabel(): string { return 'My Connector'; }
|
||||
public function getType(): string { return 'my_entity'; }
|
||||
public function getImportEntityFQCN(): string { return MyEntity::class; }
|
||||
public function getImportJobName(): string { return 'my_import_job'; }
|
||||
}
|
||||
```
|
||||
|
||||
Tag: `{ name: oro_integration.connector, type: my_entity, channel_type: my_channel }`
|
||||
|
||||
## API-Based Integration (Middleware)
|
||||
|
||||
For external API integrations not using the IntegrationBundle:
|
||||
|
||||
1. Create an HTTP client service wrapping Symfony HttpClient
|
||||
2. Store credentials in system configuration (see system-config pattern)
|
||||
3. Use MQ processors for async data sync
|
||||
4. Implement retry/backoff for resilience
|
||||
5. Use notification alerts for failure reporting
|
||||
|
||||
## Import/Export Services
|
||||
|
||||
```yaml
|
||||
services:
|
||||
acme.importexport.data_converter:
|
||||
parent: oro_importexport.data_converter.configurable
|
||||
|
||||
acme.importexport.processor.export:
|
||||
parent: oro_importexport.processor.export_abstract
|
||||
calls:
|
||||
- [setDataConverter, ['@acme.importexport.data_converter']]
|
||||
tags:
|
||||
- { name: oro_importexport.processor, type: export,
|
||||
entity: 'Acme\Bundle\ExampleBundle\Entity\Example',
|
||||
alias: acme_example }
|
||||
|
||||
acme.importexport.processor.import:
|
||||
parent: oro_importexport.processor.import_abstract
|
||||
calls:
|
||||
- [setDataConverter, ['@acme.importexport.data_converter']]
|
||||
tags:
|
||||
- { name: oro_importexport.processor, type: import,
|
||||
entity: 'Acme\Bundle\ExampleBundle\Entity\Example',
|
||||
alias: acme_example }
|
||||
```
|
||||
|
||||
## Notification Alerts
|
||||
|
||||
Use `Oro\Bundle\NotificationAlertBundle` to surface integration failures:
|
||||
|
||||
```php
|
||||
$this->notificationAlertManager->addNotificationAlert(
|
||||
NotificationAlert::createForIntegration($integration, 'Sync failed: ...')
|
||||
);
|
||||
```
|
||||
Reference in New Issue
Block a user