From 6561d4363cccc8f3f510bc3675cbc6bb43d747ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Thu, 22 Jan 2026 11:18:55 +0100 Subject: [PATCH 1/3] Described virtual products and added an example creating product type with PHP API --- .../src/Command/ProductTypeCommand.php | 57 ++++++++++++++++- .../POST/ProductType.json.example | 1 + .../POST/ProductTypeCreate.json.example | 3 +- .../POST/ProductTypeView.json.example | 1 + .../id/GET/ProductType.json.example | 1 + .../rest_api_reference/input/ibexa-types.raml | 4 ++ docs/pim/pim_guide.md | 10 +++ docs/pim/product_api.md | 61 ++++++++++++++++++- 8 files changed, 133 insertions(+), 5 deletions(-) diff --git a/code_samples/api/product_catalog/src/Command/ProductTypeCommand.php b/code_samples/api/product_catalog/src/Command/ProductTypeCommand.php index 70216e99fe..069be0767d 100644 --- a/code_samples/api/product_catalog/src/Command/ProductTypeCommand.php +++ b/code_samples/api/product_catalog/src/Command/ProductTypeCommand.php @@ -2,8 +2,12 @@ namespace App\Command; +use Ibexa\Contracts\Core\Repository\ContentTypeService; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; +use Ibexa\Contracts\ProductCatalog\AttributeDefinitionServiceInterface; +use Ibexa\Contracts\ProductCatalog\Local\LocalProductTypeServiceInterface; +use Ibexa\Contracts\ProductCatalog\Local\Values\ProductType\AssignAttributeDefinitionStruct; use Ibexa\Contracts\ProductCatalog\ProductTypeServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -18,11 +22,26 @@ final class ProductTypeCommand extends Command private ProductTypeServiceInterface $productTypeService; - public function __construct(UserService $userService, PermissionResolver $permissionResolver, ProductTypeServiceInterface $productTypeService) - { + private LocalProductTypeServiceInterface $localProductTypeService; + + private ContentTypeService $contentTypeService; + + private AttributeDefinitionServiceInterface $attributeDefinitionService; + + public function __construct( + UserService $userService, + PermissionResolver $permissionResolver, + ProductTypeServiceInterface $productTypeService, + LocalProductTypeServiceInterface $localProductTypeService, + ContentTypeService $contentTypeService, + AttributeDefinitionServiceInterface $attributeDefinitionService + ) { $this->userService = $userService; $this->permissionResolver = $permissionResolver; $this->productTypeService = $productTypeService; + $this->localProductTypeService = $localProductTypeService; + $this->contentTypeService = $contentTypeService; + $this->attributeDefinitionService = $attributeDefinitionService; parent::__construct('doc:product_type'); } @@ -41,6 +60,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int $productTypeIdentifier = $input->getArgument('productTypeIdentifier'); + $productTypeCreateStruct = $this->localProductTypeService->newProductTypeCreateStruct( + 'digital_product', + 'eng-GB' + ); + + $productTypeCreateStruct->setNames([ + 'eng-GB' => 'Digital Product', + 'pol-PL' => 'Produkt Cyfrowy', + ]); + + $productTypeCreateStruct->setVirtual(true); + + $contentTypeCreateStruct = $productTypeCreateStruct->getContentTypeCreateStruct(); + + $marketingDescriptionFieldDefinition = $this->contentTypeService->newFieldDefinitionCreateStruct( + 'marketing_description', + 'ezstring' + ); + $marketingDescriptionFieldDefinition->names = ['eng-GB' => 'Marketing Description']; + $marketingDescriptionFieldDefinition->position = 100; + $contentTypeCreateStruct->addFieldDefinition($marketingDescriptionFieldDefinition); + + $sizeAttribute = $this->attributeDefinitionService->getAttributeDefinition('size'); + + $attributeAssignment = new AssignAttributeDefinitionStruct( + $sizeAttribute, + false, + false + ); + + $productTypeCreateStruct->setAssignedAttributesDefinitions([$attributeAssignment]); + + $newProductType = $this->localProductTypeService->createProductType($productTypeCreateStruct); + $productType = $this->productTypeService->getProductType($productTypeIdentifier); $output->writeln($productType->getName()); diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductType.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductType.json.example index c12b3a1f14..95bae8997c 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductType.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductType.json.example @@ -3,6 +3,7 @@ "_media-type": "application/vnd.ibexa.api.ProductType+json", "identifier": "test_pt321", "name": "New Product Type", + "is_virtual": false, "AttributeAssignmentList": [ { "_media-type": "application/vnd.ibexa.api.AttributeAssignment+json", diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example index 261dc4c029..242a62f504 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example @@ -83,6 +83,7 @@ "is_required": false, "is_discriminator": true } - ] + ], + "is_virtual": false } } \ No newline at end of file diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeView.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeView.json.example index 3f11ee9b45..117d302a29 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeView.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeView.json.example @@ -15,6 +15,7 @@ "_media-type": "application/vnd.ibexa.api.ProductType+json", "identifier": "climbing_shoe", "name": "New Product Type", + "is_virtual": false, "AttributeAssignmentList": [ { "_media-type": "application/vnd.ibexa.api.AttributeAssignment+json", diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example index b8b41ebe62..8b503f3fef 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example @@ -3,6 +3,7 @@ "_media-type": "application/vnd.ibexa.api.ProductType+json", "identifier": "d_rope", "name": "Dynamic ropes", + "is_virtual": false, "AttributeAssignmentList": [] } } \ No newline at end of file diff --git a/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml b/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml index e8d2bdfb5c..895fd80ec9 100644 --- a/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml +++ b/docs/api/rest_api/rest_api_reference/input/ibexa-types.raml @@ -4526,6 +4526,10 @@ ProductTypeCreate: assigned_attributes: type: array items: ProductTypeAssignedAttribute + is_virtual: + type: boolean + required: false + description: 'Determines whether the product type is virtual (digital products) or physical. Defaults to false (physical).' ProductTypeUpdateWrapper: description: 'JSON object with only a ProductTypeUpdate property.' diff --git a/docs/pim/pim_guide.md b/docs/pim/pim_guide.md index 9767f4ce37..a0928ec9df 100644 --- a/docs/pim/pim_guide.md +++ b/docs/pim/pim_guide.md @@ -79,6 +79,16 @@ Before you can assign categories to products, you need to [enable product catego ![Product categories](img/product_categories.png) +### Virtual and physical products + +Product types in [[= product_name =]] can be either virtual or physical: + +- **Physical products** are tangible items that require shipping (for example: books, clothing, electronics). +- **Virtual products** are digital items that don't require physical delivery (for example: software licenses, e-books, online courses, digital downloads). + +This product type property affects the checkout process. +Virtual products skip the [shipping step](shipping_management.md) during checkout. + ### Currencies Currencies are used when calculating product price. In the system you can find a list of available currencies, but you can also create custom ones by providing its code. diff --git a/docs/pim/product_api.md b/docs/pim/product_api.md index 3d9ac22a3d..675fcf71d2 100644 --- a/docs/pim/product_api.md +++ b/docs/pim/product_api.md @@ -105,16 +105,73 @@ You can retrieve the tags (corresponding to attribute values) of assets with the To work with product types, use [`ProductTypeServiceInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductTypeServiceInterface.html). +### Creating product types + +To create a product type, use [`LocalProductTypeServiceInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalProductTypeServiceInterface.html). + +First, create a product type struct with `LocalProductTypeServiceInterface::newProductTypeCreateStruct()`, providing the identifier and main language code: + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 62, 66) =]] +``` + +You can set names in multiple languages by using `setNames()`: + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 67, 71) =]] +``` + +To create a virtual product type (for digital products that don't require shipping), use `setVirtual()`: + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 72, 73) =]] +``` + +#### Adding field definitions + +To add custom field definitions to the product type, use `getContentTypeCreateStruct()` to access the underlying content type struct. +For more information about working with content types, see [Adding content types](../content_management/content_api/managing_content.md#adding-content-types). + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 76, 83) =]] +``` + +#### Assigning attributes + +To assign product attributes to the product type, use `setAssignedAttributesDefinitions()` with an array of [`AssignAttributeDefinitionStruct`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-ProductType-AssignAttributeDefinitionStruct.html) objects. + +First, retrieve the attribute definition using [`AttributeDefinitionServiceInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AttributeDefinitionServiceInterface.html): + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 84, 85) =]] +``` + +Then create the assignment struct with the attribute definition, and set whether it's required and whether it's a discriminator (used for product variants): + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 86, 93) =]] +``` + +For more information about working with attributes through PHP API, see [Attributes](#attributes). + +Finally, create the product type with `LocalProductTypeServiceInterface::createProductType()`: + +``` php +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 94, 95) =]] +``` + +### Getting product types + Get a product type object by using `ProductTypeServiceInterface::getProductType()`: ``` php -[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 43, 44) =]] +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 96, 96) =]] ``` You can also get a list of product types with `ProductTypeServiceInterface::findProductTypes()`: ``` php -[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 47, 52) =]] +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 100, 105) =]] ``` ## Product availability From 82e583a0acd37b8153bbf76a348dc5d7e436ad45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Tue, 27 Jan 2026 13:47:58 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> --- .../catalog/product_types/POST/ProductTypeCreate.json.example | 2 +- .../catalog/product_types/id/GET/ProductType.json.example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example index 242a62f504..8fadaaf100 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/POST/ProductTypeCreate.json.example @@ -86,4 +86,4 @@ ], "is_virtual": false } -} \ No newline at end of file +} diff --git a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example index 8b503f3fef..56ab23208c 100644 --- a/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example +++ b/docs/api/rest_api/rest_api_reference/input/examples/product/catalog/product_types/id/GET/ProductType.json.example @@ -6,4 +6,4 @@ "is_virtual": false, "AttributeAssignmentList": [] } -} \ No newline at end of file +} From 0afeaaff85a06bab7fde5fed3d978fbf620eda6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Tue, 27 Jan 2026 13:48:09 +0100 Subject: [PATCH 3/3] Fixed invalid include --- docs/pim/product_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pim/product_api.md b/docs/pim/product_api.md index 675fcf71d2..517e2e8850 100644 --- a/docs/pim/product_api.md +++ b/docs/pim/product_api.md @@ -165,7 +165,7 @@ Finally, create the product type with `LocalProductTypeServiceInterface::createP Get a product type object by using `ProductTypeServiceInterface::getProductType()`: ``` php -[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 96, 96) =]] +[[= include_file('code_samples/api/product_catalog/src/Command/ProductTypeCommand.php', 96, 97) =]] ``` You can also get a list of product types with `ProductTypeServiceInterface::findProductTypes()`: