Files

343 lines
10 KiB
Markdown

---
name: oro-api-dev
description: OroCommerce API development reference covering JSON:API configuration, entity exposure, processors, filters, actions, authentication, CORS, batch API, storefront API, and testing. Use when configuring API resources, creating custom API processors, exposing entities via REST, building API integrations, or testing API endpoints in OroCommerce.
---
# OroCommerce API Developer
Reference for API development on OroCommerce.
Docs: https://doc.oroinc.com/backend/api/
## Overview
Oro API implements REST conforming to [JSON:API specification](http://jsonapi.org/).
Built on `OroApiBundle` using ChainProcessor, EntitySerializer, and Symfony Forms.
Base URL: `https://{host}/api/{resource}`
Sandbox: `https://{host}/api/doc`
By default, only custom entities, dictionaries, and enumerations are accessible.
Other entities must be explicitly enabled.
## Enabling an Entity
Create `Resources/config/oro/api.yml`:
```yaml
api:
entities:
Acme\Bundle\ExampleBundle\Entity\Example: ~
```
Then run:
```bash
php bin/console oro:api:cache:clear
php bin/console oro:api:doc:cache:clear # rebuild sandbox docs
```
## Configuration Reference
```yaml
api:
entities:
Acme\Bundle\ExampleBundle\Entity\Example:
# Exclude entity entirely
exclude: false
# Field configuration
fields:
id: ~
name:
description: "Human-readable name"
secretField:
exclude: true # hide from API
renamedField:
property_path: original_name # map API name to entity property
computedField:
data_type: string
property_path: _ # custom processor handles this
# Filter configuration
filters:
fields:
name: ~
status:
allow_array: true # ?filter[status][]=a&filter[status][]=b
createdAt:
exclude: false
# Sorter configuration
sorters:
fields:
name: ~
createdAt: ~
# Action configuration
actions:
get: true
get_list: true
create: true
update: true
delete: false # disable delete
delete_list: false # disable bulk delete
# Subresources
subresources:
relatedItems:
actions:
get_subresource:
exclude: false
```
## Actions
Standard CRUD actions available per entity:
| Action | HTTP | URL | Description |
|---|---|---|---|
| get | GET | /api/{resource}/{id} | Single resource |
| get_list | GET | /api/{resource} | Collection |
| create | POST | /api/{resource} | Create |
| update | PATCH | /api/{resource}/{id} | Update |
| delete | DELETE | /api/{resource}/{id} | Delete |
| delete_list | DELETE | /api/{resource} | Bulk delete |
| get_subresource | GET | /api/{resource}/{id}/{sub} | Subresource |
| get_relationship | GET | /api/{resource}/{id}/relationships/{rel} | Relationship |
| update_relationship | PATCH | /api/{resource}/{id}/relationships/{rel} | Set relationship |
| add_relationship | POST | /api/{resource}/{id}/relationships/{rel} | Add to relationship |
| delete_relationship | DELETE | /api/{resource}/{id}/relationships/{rel} | Remove from relationship |
## Adding Filters to Existing Oro Entities
Expose custom fields on Oro core entities as API filters without modifying core code:
```yaml
api:
entities:
Oro\Bundle\CustomerBundle\Entity\CustomerUser:
filters:
fields:
erp_contact_id:
data_type: integer
property_path: erp_contact_id
Oro\Bundle\CustomerBundle\Entity\Customer:
filters:
fields:
erp_customer_id:
data_type: integer
property_path: erp_customer_id
```
This lets API consumers query: `GET /api/customerusers?filter[erp_contact_id]=12345`
The `property_path` must match an extended field added via migration (`oro_options`).
## Custom Processors
Processors modify request/response handling in the API pipeline.
Each action has a chain of processors executed in order.
```php
namespace Acme\Bundle\ExampleBundle\Api\Processor;
use Oro\Bundle\ApiBundle\Processor\CustomizeLoadedData\CustomizeLoadedDataContext;
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
class ComputeExampleField implements ProcessorInterface
{
public function process(ContextInterface $context): void
{
/** @var CustomizeLoadedDataContext $context */
$data = $context->getData();
// modify $data
$data['computedField'] = 'computed_value';
$context->setData($data);
}
}
```
Register with tag:
```yaml
services:
acme.api.processor.compute_example_field:
class: Acme\Bundle\ExampleBundle\Api\Processor\ComputeExampleField
tags:
- name: oro.api.processor
action: customize_loaded_data
class: Acme\Bundle\ExampleBundle\Entity\Example
```
### Processor Tag Attributes
| Attribute | Purpose | Example Values |
|---|---|---|
| `action` | Which API action to hook | `get`, `get_list`, `create`, `update`, `delete`, `customize_loaded_data` |
| `group` | Pipeline stage within the action | `initialize`, `normalize_input`, `security_check`, `transform_data`, `normalize_result` |
| `priority` | Execution order (higher = earlier) | `-10` (after defaults), `250` (early) |
| `class` | Entity class to scope processor to | `Oro\Bundle\CustomerBundle\Entity\CustomerUser` |
### Intercepting Create/Update with FormContext
Use `FormContext` to modify request data during create/update (e.g., resolve a custom field to a relationship):
```php
use Oro\Bundle\ApiBundle\Model\Error;
use Oro\Bundle\ApiBundle\Processor\FormContext;
use Oro\Bundle\ApiBundle\Request\Constraint;
use Oro\Component\ChainProcessor\ContextInterface;
use Oro\Component\ChainProcessor\ProcessorInterface;
class ResolveCustomerFromErpId implements ProcessorInterface
{
public function __construct(private ManagerRegistry $registry) {}
public function process(ContextInterface|FormContext $context): void
{
$requestData = $context->getRequestData();
$erpId = $requestData['erp_customer_id'] ?? null;
if ($erpId === null) {
return;
}
$customer = $this->registry
->getRepository(Customer::class)
->findOneBy(['erp_customer_id' => $erpId]);
if ($customer !== null) {
$requestData['customer'] = [
'class' => Customer::class,
'id' => $customer->getId()
];
$context->setRequestData($requestData);
}
}
}
```
Register on the `create` action in `transform_data` group:
```yaml
services:
acme.api.processor.resolve_customer:
class: Acme\Bundle\ExampleBundle\Api\Processor\ResolveCustomerFromErpId
arguments: ['@doctrine']
tags:
- { name: oro.api.processor, action: create, group: transform_data, priority: -40, class: Oro\Bundle\CustomerBundle\Entity\CustomerUser }
```
## Authentication
API supports OAuth 2.0:
```bash
# Get token
curl -X POST https://host/oauth2-token \
-d "grant_type=client_credentials" \
-d "client_id=<id>" \
-d "client_secret=<secret>"
# Use token
curl -H "Authorization: Bearer <token>" \
-H "Accept: application/vnd.api+json" \
https://host/api/examples
```
Configure OAuth apps in admin: System > User Management > OAuth Applications.
## Filters
```
GET /api/examples?filter[name]=test
GET /api/examples?filter[status][]=active&filter[status][]=pending
GET /api/examples?filter[createdAt]>2024-01-01
GET /api/examples?page[number]=2&page[size]=10
GET /api/examples?sort=-createdAt,name
GET /api/examples?include=relatedEntity
GET /api/examples?fields[examples]=name,status
```
## Batch API
For bulk operations:
```
POST /api/examples/batch
Content-Type: application/vnd.api+json
{ "data": [ { "type": "examples", "attributes": { ... } }, ... ] }
```
Async batch API processes large datasets via MQ.
Docs: https://doc.oroinc.com/api/batch-api/
## Storefront API
Separate API for storefront (customer-facing).
Configure in `Resources/config/oro/api_frontend.yml`:
```yaml
api:
entities:
Acme\Bundle\ExampleBundle\Entity\Example:
# storefront-specific config
```
Different authentication (customer user tokens).
Docs: https://doc.oroinc.com/backend/api/storefront/
## CORS Configuration
```yaml
# config/config.yml
oro_api:
cors:
preflight_max_age: 600
allow_origins:
- 'https://frontend.example.com'
allow_headers:
- 'Content-Type'
- 'Authorization'
expose_headers:
- 'X-Include'
allow_credentials: true
```
## Testing API
Oro provides base test cases for API functional tests:
```php
use Oro\Bundle\ApiBundle\Tests\Functional\RestJsonApiTestCase;
class ExampleApiTest extends RestJsonApiTestCase
{
public function testGetList(): void
{
$response = $this->cget(['entity' => 'examples']);
$this->assertResponseContains('expected_response.yml', $response);
}
public function testCreate(): void
{
$response = $this->post(['entity' => 'examples'], 'create_request.yml');
$this->assertResponseContains('create_response.yml', $response);
}
}
```
Test fixtures in `Tests/Functional/Api/DataFixtures/`.
Expected responses in `Tests/Functional/Api/responses/`.
## CLI Commands
```bash
php bin/console oro:api:cache:clear # clear API config cache
php bin/console oro:api:doc:cache:clear # rebuild sandbox docs
php bin/console oro:api:config:dump-reference # dump config structure
php bin/console oro:api:debug # list available API resources
php bin/console oro:api:debug --sub-resources Example # show entity subresources
```