Notice: You are browsing the documentation for PrestaShop 9, which is currently in development.
You might want to look at the current version, PrestaShop 8. Read the current version of the documentation
API Platform has many different ways to configure and define APIs (PHP, YAML, XML). We chose the PHP approach relying on PHP attributes to simplify and centralize the configuration in a single file, which is why:
We configured PrestaShop so that it automatically loads API resource classes from the core AND from the modules (handled by PrestaShopExtension
) as long as the following convention is respected:
src/PrestaShopBundle/ApiPlatform/Resources
src/ApiPlatform/Resources
The endpoints defined in modules resources are only usable when the module is installed and enabled. However, the scopes defined in the modules are scanned as long as the module is installed, this allows assigning them to clients before you enable the module and its endpoints.
The core API is based on CQRS integration, to simplify the configuration we created custom operations that can be used to configure the endpoints.
The following examples display each operation separately, but you can define them all in the same class (as long as they share the same fields and a common DTO makes sense).
HTTP Method | Action |
---|---|
GET | Read a single resource |
<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Query\GetApiClientForEditing;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSGet;
#[ApiResource(
operations: [
new CQRSGet(
uriTemplate: '/api-client/{apiClientId}',
requirements: ['apiClientId' => '\d+'],
CQRSQuery: GetApiClientForEditing::class,
scopes: ['api_client_read']
),
],
exceptionToStatus: [ApiClientNotFoundException::class => 404],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
public string $clientId;
public string $clientName;
public string $description;
public ?string $externalIssuer;
public bool $enabled;
public int $lifetime;
public array $scopes;
}
HTTP Method | Action |
---|---|
POST | Create a new resource |
In this example AddApiClientCommand
returns a CreatedApiClient
object, so the response will be built based on this returned object.
If you want the endpoint to fetch and return the whole object you can specify a CQRSQuery
parameter (see the following update example).
<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\AddApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSCreate;
#[ApiResource(
operations: [
new CQRSCreate(
uriTemplate: '/api-client',
CQRSCommand: AddApiClientCommand::class,
scopes: ['api_client_write']
),
],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
public string $clientId;
public string $clientName;
public string $description;
public ?string $externalIssuer;
public bool $enabled;
public int $lifetime;
public array $scopes;
/**
* Only used for the return of created API Client, it is the only endpoint where the secret is returned.
*
* @var string
*/
public string $secret;
}
HTTP Method | Action |
---|---|
PATCH | Update a resource partially (not all fields are required, the missing ones are ignored and not modified) |
In this example we want the endpoint to return the state of the updated resource, so we define CQRSQuery
parameter with the CQRS query that fetches it, the result will we serialized in the same format as the GET operation above.
<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\EditApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Query\GetApiClientForEditing;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSPartialUpdate;
#[ApiResource(
operations: [
new CQRSPartialUpdate(
uriTemplate: '/api-client/{apiClientId}',
read: false,
CQRSCommand: EditApiClientCommand::class,
CQRSQuery: GetApiClientForEditing::class,
scopes: ['api_client_write']
),
],
exceptionToStatus: [ApiClientNotFoundException::class => 404],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
public string $clientId;
public string $clientName;
public string $description;
public ?string $externalIssuer;
public bool $enabled;
public int $lifetime;
public array $scopes;
}
HTTP Method | Action |
---|---|
PUT | Update a resource by replacing its whole content (you must specify all the fields or they will be considered empty) |
<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\EditApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Query\GetApiClientForEditing;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate;
#[ApiResource(
operations: [
new CQRSUpdate(
uriTemplate: '/api-client/{apiClientId}',
read: false,
CQRSCommand: EditApiClientCommand::class,
CQRSQuery: GetApiClientForEditing::class,
scopes: ['api_client_write']
),
],
exceptionToStatus: [ApiClientNotFoundException::class => 404],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
public string $clientId;
public string $clientName;
public string $description;
public ?string $externalIssuer;
public bool $enabled;
public int $lifetime;
public array $scopes;
}
HTTP Method | Action |
---|---|
DELETE | Delete a single resource |
<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Command\DeleteApiClientCommand;
use PrestaShop\PrestaShop\Core\Domain\ApiClient\Exception\ApiClientNotFoundException;
use PrestaShopBundle\ApiPlatform\Metadata\CQRSDelete;
#[ApiResource(
operations: [
new CQRSDelete(
uriTemplate: '/api-client/{apiClientId}',
requirements: ['apiClientId' => '\d+'],
output: false,
CQRSQuery: DeleteApiClientCommand::class,
scopes: ['api_client_write']
),
],
exceptionToStatus: [ApiClientNotFoundException::class => 404],
)]
class ApiClient
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
}
For listing operations we provided a custom operation based on the core grid system based on two settings:
GridDataFactoryInterface
(also used in all migrated pages listing)Filters
class to used, it is optional but if you specify a custom one then you can force the default values<?php
declare(strict_types=1);
namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\ApiClient;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use PrestaShop\PrestaShop\Core\Search\Filters\ApiClientFilters;
use PrestaShopBundle\ApiPlatform\Metadata\PaginatedList;
#[ApiResource(
operations: [
new PaginatedList(
uriTemplate: '/api-clients',
scopes: [
'api_client_read',
],
ApiResourceMapping: [
'[id_api_client]' => '[apiClientId]',
'[client_id]' => '[clientId]',
'[client_name]' => '[clientName]',
'[external_issuer]' => '[externalIssuer]',
],
gridDataFactory: 'prestashop.core.grid.data_factory.api_client',
filtersClass: ApiClientFilters::class,
filtersMapping: [
'[apiClientId]' => '[id_api_client]',
'[clientId]' => '[client_id]',
'[clientName]' => '[client_name]',
'[externalIssuer]' => '[external_issuer]',
],
),
],
normalizationContext: ['skip_null_values' => false],
)]
class ApiClientList
{
#[ApiProperty(identifier: true)]
public int $apiClientId;
public string $clientId;
public string $clientName;
public string $description;
public ?string $externalIssuer;
public bool $enabled;
public int $lifetime;
}
The API Platform DTO allows you to define the expected format of your API endpoint, including the format of each field name (snake case, camel case, etc.) and any field you want to rename because it seems better.
However, the CQRS or Grid implementation may not match the format or naming you expected, which is why you can define some mapping to explain how to match the fields between your DTO and the underlying implementation.
Each mapping is an associative array. The keys are the original naming, and the value is the target mapping (the renamed key if you prefer). You can customize different mappings that are used at different moments of normalization in the workflow.
Mapping field | Usage |
---|---|
CQRSQueryMapping | Used to normalize/denormalize CQRS query objects AND CQRS QueryResult objects |
CQRSCommandMapping | Used to normalize/denormalize CQRS command objects |
ApiResourceMapping | Used to normalize/denormalize Api Resource DTO objects |
filtersMapping | Used to normalize the Filters object |
To clarify when each mapping is used for normalization or denormalization here are the details of each workflow: