- Turn on API for an Entity
- Turn on API for an Entity Disabled in "Resources/config/oro/entity.yml"
- Change an ACL Resource for an Action
- Disable Access Checks for an Action
- Disable an Entity Action
- Change the Delete Handler for an Entity
- Change the Maximum Number of Entities that Can Be Deleted by One Request
- Configure a Nested Object
- Configure a Nested Association
- Configure an Extended Many-To-One Association
- Configure an Extended Many-To-Many Association
- Configure an Extended Multiple Many-To-One Association
- Add a Custom Controller
- Add a Custom Route
- Using a Non-primary Key to Identify an Entity
- Enable API for an Entity Without Identifier
- Enable Custom API
By default, API for entities is disabled. To turn on API for an entity, add the entity to Resources/config/oro/api.yml
of your bundle:
api:
entities:
Acme\Bundle\ProductBundle\Product: ~
The exclusions
section of the Resources/config/oro/entity.yml
configuration file is used to make an entity or a field inaccessible for a user. The entities and fields from this section are inaccessible via the data API as well. However, it is possible to override this rule for the data API. To do this, use the exclude
option in Resources/config/oro/api.yml
.
Let us consider the case when you have the following Resources/config/oro/entity.yml
:
oro_entity:
exclusions:
- { entity: Acme\Bundle\AcmeBundle\Entity\AcmeEntity1 }
- { entity: Acme\Bundle\AcmeBundle\Entity\AcmeEntity2, field: field1 }
To override these rules in the data API, add the following lines to the Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\AcmeBundle\Entity\AcmeEntity1:
exclude: false # override exclude rule from entity.yml
Acme\Bundle\AcmeBundle\Entity\AcmeEntity2:
fields:
field1:
exclude: false # override exclude rule from entity.yml
By default, the following permissions are used to restrict access to an entity in a scope of the specific action:
Action | Permission |
---|---|
get | VIEW |
get_list | VIEW |
delete | DELETE |
delete_list | DELETE |
create | CREATE and VIEW |
update | EDIT and VIEW |
get_subresource | VIEW |
get_relationship | VIEW |
update_relationship | EDIT and VIEW |
add_relationship | EDIT and VIEW |
delete_relationship | EDIT and VIEW |
If you want to change permission or disable access checks for some action, you can use the acl_resource
option of the actions
configuration section.
For example, to change permissions for the delete
action, add the following lines to the Resources/config/oro/api.yml
of your bundle:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
delete:
acl_resource: access_entity_view
If there is the access_entity_view
ACL resource:
access_entity_view:
type: entity
class: Acme\Bundle\ProductBundle\Product
permission: VIEW
As a result, the VIEW
permission will be used instead of the DELETE
permission.
You can disable access checks for some action by setting null
as a value for the acl_resource
option in Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
get_list:
acl_resource: ~
When you add an entity to the API, all the actions will be available by default.
If an action should be inaccessible, disable it in Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
delete:
exclude: true
You can use the short syntax:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
delete: false
By default, entity deletion is processed by DeleteHandler.
If your want to use another delete handler, set it using the delete_handler
option in Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\ProductBundle\Product:
delete_handler: acme.demo.product_delete_handler
Please note that the value of the delete_handler
option is a service id.
Additionally, you can create a custom delete handler. The handler class must be derived from DeleteHandler.
By default, the delete_list action can delete not more than 100 entities. This limit is set by the SetDeleteLimit processor.
If your want to use another limit, set it using the max_results
option in Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
delete_list:
max_results: 200
You can remove the limit at all. To do this, set -1
as a value for the max_results
option:
api:
entities:
Acme\Bundle\ProductBundle\Product:
actions:
delete_list:
max_results: -1
Sometimes it is required to group several fields and expose them as a nested object in the data API. For example, consider the case when an entity has two fields intervalNumber
and intervalUnit
but you need to expose them in API as number
and unit
properties of the interval
field. To achieve it, use the following configuration:
api:
entities:
Oro\Bundle\ReminderBundle\Entity\Reminder:
fields:
interval:
data_type: nestedObject
form_options:
data_class: Oro\Bundle\ReminderBundle\Model\ReminderInterval
by_reference: false
fields:
number:
property_path: intervalNumber
unit:
property_path: intervalUnit
intervalNumber:
exclude: true
intervalUnit:
exclude: true
Please note that an entity, in this example Oro\Bundle\ReminderBundle\Entity\Reminder, should have the setInterval
method. This method is called by the create and update actions to set the nested object.
Here is an example of how the nested objects look in JSON.API:
{
"data": {
"type": "reminders",
"id": "1",
"attributes": {
"interval": {
"number": 2,
"unit": "H"
}
}
}
}
Sometimes a relationship with a group of entities is implemented as two fields, "entityClass" and "entityId", rather than many-to-one extended association. But in the data API these fields should be represented as a regular relationship. To achieve this, a special data type named nestedAssociation
was implemented. For example, let us suppose that an entity has two fields sourceEntityClass
and sourceEntityId
and you need to expose them in API as the source
relationship. To achieve this, use the following configuration:
api:
entities:
Oro\Bundle\OrderBundle\Entity\Order:
fields:
source:
data_type: nestedAssociation
fields:
__class__:
property_path: sourceEntityClass
id:
property_path: sourceEntityId
Here is an example of how the nested association looks in JSON.API:
{
"data": {
"type": "orders",
"id": "1",
"relationships": {
"source": {
"type": "contacts",
"id": 123
}
}
}
}
Please note that fields used in a nested association, in this example sourceEntityClass
and sourceEntityId
, are automatically excluded from the result and you do not need to mark them with exclude
option. Moreover, they will be excluded even if you mark them with exclude: false
in a configuration file.
For information about extended associations, see the Associations topic.
Depending on the current entity configuration, each association resource (e.g. attachment) can be assigned to one of the resources (e.g. user, account, contact) that support such associations.
By default, there is no possibility to retrieve targets of such associations. To make targets available for retrieving, enable this in Resources/config/oro/api.yml
:
api:
entities:
Oro\Bundle\AttachmentBundle\Entity\Attachment:
fields:
target:
data_type: association:manyToOne
After applying the configuration, the target
relationship becomes available for the get_list, get, create, and update actions. The target
relationship becomes also available as a subresource and thus, it is possible to perform the get_subresource, get_relationship and update_relationship actions.
The data_type
parameter has format: association:relationType:associationKind
, where:
relationType
part should have the "manyToOne: value for the extended Many-To-One association;associationKind
is the optional part that represents the kind of the association.
For information about extended associations, see the Associations topic.
Depending on the current entity configuration, each association resource (e.g. call) can be assigned to several resources (e.g. user, account, contact) that support such associations.
By default, there is no possibility to retrieve targets of such associations. To make targets available for retrieving, enable this in Resources/config/oro/api.yml
:
api:
entities:
Oro\Bundle\CallBundle\Entity\Call:
fields:
activityTargets:
data_type: association:manyToMany:activity
After applying the configuration, the activityTargets
relationship becomes available in scope of the following actions:
The activityTargets
relationship also becomes available as a subresource and thus, it is possible to perform the following actions:
The data_type
parameter has format: association:relationType:associationKind
, where:
relationType
part should have the 'manyToMany' value for the extended Many-To-Many association;associationKind
is the optional part that represents the kind of the association.
For information about extended associations, see the Associations topic.
Depending on the current entity configuration, each association resource (e.g. call) can be assigned to several resources (e.g. user, account, contact) that support such associations. However, in case of multiple many-to-one association, a resource can be associated with only one other resource of each type. For example, a call can be associated only with one user, one account, etc.
By default, there is no possibility to retrieve targets of such associations. To make targets available for retrieving, enable this in Resources/config/oro/api.yml
:
api:
entities:
Oro\Bundle\CallBundle\Entity\Call:
fields:
targets:
data_type: association:multipleManyToOne
After applying the configuration, the targets
relationship becomes available in scope of the following actions:
The targets
relationship also becomes available as a subresource and thus, it is possible to perform the following actions:
The data_type
parameter has format: association:relationType:associationKind
, where:
relationType
part should have the 'multipleManyToOne' value for the extended Multiple Many-To-One association;associationKind
is the optional part that represents the kind of the association.
By default, all REST API resources are handled by RestApiController that handles the following actions:
- get_list
- get
- delete
- delete_list
- create
- update
- get_subresource
- get_relationship
- update_relationship
- add_relationship
- delete_relationship
If this controller cannot handle the implementation of your REST API resources, you can register a custom controller. Please note that this is not recommended and should be used only in very special cases. Having a custom controller implies that a lot of logic is to be implemented from scratch, including:
- extracting and validation of the input data
- building and formatting the output document
- error handling
- loading data from the database
- saving data to the database
- implementing relationships with other API resources
- documenting such API resources
If you know about these disadvantages and still want to proceed, to register a custom controller, perform the following steps:
- Create a controller.
- Register the created controller using the
Resources/config/oro/routing.yml
configuration file.
Here is an example of the controller:
<?php
namespace Acme\Bundle\AppBundle\Controller\Api;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
class MyResourceController extends Controller
{
/**
* Retrieve a specific record.
*
* @param Request $request
*
* @ApiDoc(
* resource=true,
* description="Get a resource",
* views={"rest_json_api"},
* section="myresources",
* requirements={
* {
* "name"="id",
* "dataType"="integer",
* "requirement"="\d+",
* "description"="The 'id' requirement description."
* }
* },
* filters={
* {
* "name"="aFilter",
* "dataType"="string",
* "requirement"=".+",
* "description"="The 'aFilter' filter description."
* }
* },
* output={
* "class"="Your\Namespace\Class",
* "fields"={
* {
* "name"="aField",
* "dataType"="string",
* "description"="The 'aField' field description."
* }
* }
* },
* statusCodes={
* 200="Returned when successful",
* 500="Returned when an unexpected error occurs"
* }
* )
*
* @return Response
*/
public function getAction(Request $request)
{
// @todo: add an implementaution here
}
}
An example of the Resources/config/oro/routing.yml
configuration file:
acme_api_get_my_resource:
path: /api/myresources/{id}
methods: [GET]
defaults:
_controller: AcmeAppBundle:Api\MyResource:get
options:
group: rest_api
For information about the ApiDoc
annotation, see Symfony documentation.
To learn about all possible properties of the fields
option, see AbstractFormatter class in NelmioApiDocBundle. Please note that the fields
option can be used inside the input
and output
options.
Use the oro:api:doc:cache:clear command to apply changes in the ApiDoc
annotation to API Sandbox.
As desctibed in Add a Custom Controller, RestApiController handles all registered REST API resources, and in the most cases you do not need to change this.
But sometimes you need to change default mapping between URI and an action of this controller for some
REST API resources.
For example, imagine REST API resource for a profile of the logged in user. Let's imagine that URI of this
resource should be /api/userprofile
. If you take a look at routing.yml
you will see that this URI is matched by /api/{entity}
pattern, but the action that handles this
pattern works with a list of entities, not with a single entity. The challenge is to map /api/userprofile
to
OroApiBundle:RestApi:item
action that works with a single entity and to remove handling of
/api/userprofile/{id}
. This can be achieved using own route definition with override_path
option.
Here is an example of the Resources/config/oro/routing.yml
configuration file:
acme_rest_api_user_profile:
path: /api/userprofile
defaults:
_controller: OroApiBundle:RestApi:item
entity: userprofile
options:
group: rest_api
override_path: /api/userprofile/{id}
By default, a primary key is used to identify ORM entities in API. If you need another field as an identifier, specify it using the identifier_field_names
option.
For example, let your entity has the id
field that is the primary key and the uuid
field that contains a unique value for each entity. To use the uuid
field to identify the entity, add the following in Resources/config/oro/api.yml
:
api:
entities:
Acme\Bundle\AppBundle\Entity\SomeEntity:
identifier_field_names: ['uuid']
You can also exclude the id
field (primary key) if you do not want to expose it via API:
api:
entities:
Acme\Bundle\AppBundle\Entity\SomeEntity:
identifier_field_names: ['uuid']
fields:
id:
exclude: true
Sometimes it is required to create API resource that does not have an identifier. An example of such API resources can be resources for registering a new account or logging in a user.
The following steps describes how to create such API resources:
-
Create a PHP class that will represent API resource. Usualy such classes are named as models and located in
Api/Model
directory. For example:<?php namespace Acme\Bundle\AppBundle\Api\Model; class Account { /** @var string|null */ private $name; /** * @return string|null */ public function getName() { return $this->name; } /** * @param string|null $name */ public function setName($name) { $this->name = $name; } }
-
Desctibe the model via
Resources/config/oro/api.yml
configuration file in your bundle, e.g.:api: entity_aliases: Acme\Bundle\AppBundle\Api\Model\Account: alias: registeraccount plural_alias: registeraccount entities: Acme\Bundle\AppBundle\Api\Model\Account: fields: name: data_type: string description: The user name form_options: constraints: - NotBlank: ~ actions: create: description: Register a new account get: false update: false delete: false
-
Register a route via
Resources/config/oro/routing.yml
configuration file in your bundle usingOroApiBundle:RestApi:itemWithoutId
as a controller, e.g.:acme_rest_api_register_account: path: /api/registeraccount defaults: _controller: OroApiBundle:RestApi:itemWithoutId entity: registeraccount options: group: rest_api
-
Create a processor to handle data, e.g.:
<?php namespace Acme\Bundle\AppBundle\Api\Processor; use Acme\Bundle\AppBundle\Api\Model\Account; use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; class RegisterAccount implements ProcessorInterface { /** * {@inheritdoc} */ public function process(ContextInterface $context) { /** @var Account $account */ $account = $context->getResult(); // implement registration of a new account } }
-
Register a processor in the depencency injection container, e.g.:
services: acme.api.register_account: class: Acme\Bundle\AppBundle\Api\Processor\RegisterAccount tags: - { name: oro.api.processor, action: create, group: save_data, class: Acme\Bundle\AppBundle\Api\Model\Account }
Before begin please ensure that you are familiar with API request type.
Lets imagine you need API that will be used for an integration with some ERP system. In this case,
to simplify the development and to avoid unnecessary API calls, it will be good if your API resources will have
the same identifiers as the ERP system. The easiest way to achieve this is to create erpId
field for each entity
and map this field as the identifier of API resource via
identifier_field_names configuration option. But the drawback of this
approach is that you have to change existing API, and as the result it may lead to failure of existing API clients.
To avoid this you can keep existing API unchanged and create a new type of API that will have all features
of existing API and will have modifications specific for this new integration as well.
To do this you need to follow several simple steps:
-
Decide how to API clients should inform server that they need to work with new type of API. The simplest way is to use custom HTTP header. If a client sends this header it will work with new API, if it does not it will work with already existing API. Lets assume that we will use
X-Integration-Type
header to switch API types. If this header is sent and its value isERP
the new API will be used; otherwise, the already existing API will be used. -
Decide which name of the request type you will use for the new API. Lets assume it will be
erp
. -
Decide which name of API configuration files you will use to add modifications specific for the new API. Lets assume it will be
api_erp.yml
. -
Add the new type of API to ApiBundle and configure API Sandbox via
Resources/config/oro/app.yml
configuration file in your bundle:oro_api: # add API type for ERP integration config_files: erp: # load API configuration for ERP integration from two types of files, api_erp.yml and api.yml # the first file has higher priority and any configuration in this file will override # configuration from the second one file_name: [api_erp.yml, api.yml] # use this configuration only if ERP integration API is requested request_type: ['erp'] # configure API Sandbox api_doc_views: erp_rest_json_api: label: ERP Integration headers: Content-Type: application/vnd.api+json X-Integration-Type: ERP request_type: ['rest', 'json_api', 'erp']
-
Create a processor that will check the request header and add
erp
request type to the execution context of processors:<?php namespace Acme\Bundle\AppBundle\Api\Processor; use Oro\Component\ChainProcessor\ContextInterface; use Oro\Component\ChainProcessor\ProcessorInterface; use Oro\Bundle\ApiBundle\Processor\Context; class CheckErpRequestType implements ProcessorInterface { const REQUEST_HEADER_NAME = 'X-Integration-Type'; const REQUEST_HEADER_VALUE = 'ERP'; const REQUEST_TYPE = 'erp'; /** * {@inheritdoc} */ public function process(ContextInterface $context) { /** @var Context $context */ $requestType = $context->getRequestType(); if (!$requestType->contains(self::REQUEST_TYPE) && self::REQUEST_HEADER_VALUE === $context->getRequestHeaders()->get(self::REQUEST_HEADER_NAME) ) { $requestType->add(self::REQUEST_TYPE); } } }
-
Register this processor in the dependency injection container via
Resources/config/services.yml
file:acme.api.erp.check_erp_request_type: class: Acme\Bundle\AppBundle\Api\Processor\CheckErpRequestType tags: - { name: oro.api.processor, action: get, group: initialize, priority: 250 } - { name: oro.api.processor, action: get_list, group: initialize, priority: 250 } - { name: oro.api.processor, action: delete, group: initialize, priority: 250 } - { name: oro.api.processor, action: delete_list, group: initialize, priority: 250 } - { name: oro.api.processor, action: create, group: initialize, priority: 250 } - { name: oro.api.processor, action: update, group: initialize, priority: 250 } - { name: oro.api.processor, action: get_subresource, group: initialize, priority: 250 } - { name: oro.api.processor, action: get_relationship, group: initialize, priority: 250 } - { name: oro.api.processor, action: delete_relationship, group: initialize, priority: 250 } - { name: oro.api.processor, action: add_relationship, group: initialize, priority: 250 } - { name: oro.api.processor, action: update_relationship, group: initialize, priority: 250 }
-
Execute
cache:clear
command to apply the changes andoro:api:doc:cache:clear
command to build API Sandbox.
That is all. Now you can open API Sandbox
and check that it has ERP Integration
link at the top. Click on this link and try to perform any API request.
To configure the new API use Resources/config/oro/api_erp.yml
configuration file.
All API processors related to the new API should be registered with requestType: erp
attribute
for oro.api.processor
tag, e.g.:
acme.api.erp.do_something:
class: Acme\Bundle\AppBundle\Api\Processor\DoSomething
tags:
- { name: oro.api.processor, action: get, group: initialize, requestType: erp, priority: -10 }
For more details about the configuration and processors see Configuration Reference, Actions and Processors.