diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index ef49ce2f..84fe3973 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -12,6 +12,7 @@ ->in(__DIR__ . '/src/Services/CRM/Quote/') ->in(__DIR__ . '/src/Services/CRM/Lead/') ->in(__DIR__ . '/src/Services/CRM/Currency/') + ->in(__DIR__ . '/src/Services/Entity/Section/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8091e0ea..1c32a2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,22 @@ # b24-php-sdk change log + ## UPCOMING 1.5.0 – 2025.08.01 ### Added +- Added service `Services\Entity\Section\Service\Section` with support methods, + see [crm.entity.section.* methods](https://github.com/bitrix24/b24phpsdk/issues/200): + - `get` retrieve a list of storage sections, with batch calls support + - `add` add a storage section, with batch calls support + - `update` update a storage section, with batch calls support + - `delete` delete a storage section, with batch calls support +- Added service `Services\Entity\Item\Property\Service\Section` with support methods: + - `get` retrieve a list of additional properties of storage elements, with batch calls support + - `add` add an additional property to storage elements, with batch calls support + - `update` update an additional property of storage elements, with batch calls support + - `delete` delete an additional property of storage elements, with batch calls support + ### Fixed - Fixed typehints in Contact batch for method `add`, [see details](https://github.com/bitrix24/b24phpsdk/issues/202) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 87d2a095..dc2e6380 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -26,6 +26,7 @@ parameters: - tests/Integration/Services/CRM/Quote/Service/QuoteProductRowsTest.php - tests/Integration/Services/CRM/Lead/Service/LeadUserfieldTest.php - tests/Integration/Services/CRM/Currency + - tests/Integration/Services/Entity/Section bootstrapFiles: - tests/bootstrap.php parallel: diff --git a/rector.php b/rector.php index 58bb6267..0444d310 100644 --- a/rector.php +++ b/rector.php @@ -50,6 +50,8 @@ __DIR__ . '/tests/Integration/Services/CRM/Quote/Service', __DIR__ . '/src/Services/CRM/Currency', __DIR__ . '/tests/Integration/Services/CRM/Currency', + __DIR__ . '/src/Services/Entity/Section', + __DIR__ . '/tests/Integration/Services/Entity/Section', __DIR__ . '/tests/Unit/', ]) ->withCache(cacheDirectory: __DIR__ . '.cache/rector') diff --git a/src/Core/Batch.php b/src/Core/Batch.php index beffd15f..cda1698b 100644 --- a/src/Core/Batch.php +++ b/src/Core/Batch.php @@ -34,6 +34,16 @@ */ class Batch implements BatchOperationsInterface { + protected const ENTITY_METHODS = [ + 'entity.item.delete', + 'entity.section.delete', + 'entity.item.get', + 'entity.section.get', + 'entity.item.update', + 'entity.section.update', + 'entity.item.property.update', + ]; + protected const MAX_BATCH_PACKET_SIZE = 50; protected const MAX_ELEMENTS_IN_PAGE = 50; @@ -144,7 +154,7 @@ public function deleteEntityItems( $parameters = $useFieldsInsteadOfId ? ['fields' => $itemId] : ['ID' => $itemId]; // TODO: delete after migration to RestAPI v2 - if ($apiMethod === 'entity.item.delete') { + if (in_array($apiMethod, self::ENTITY_METHODS)) { $parameters = array_merge($parameters, $additionalParameters); } @@ -218,7 +228,7 @@ public function updateEntityItems(string $apiMethod, array $entityItems): Genera ); } - if ($apiMethod !== 'entity.item.update') { + if (!in_array($apiMethod, self::ENTITY_METHODS)) { if (!array_key_exists('fields', $entityItem)) { throw new InvalidArgumentException( sprintf('array key «fields» not found in entity item with id %s', $entityItemId) @@ -482,7 +492,7 @@ public function getTraversableList( // todo wait new api version if ($apiMethod !== 'user.get') { $defaultOrderKey = 'order'; - $orderKey = $apiMethod === 'entity.item.get' ? 'SORT' : $defaultOrderKey; + $orderKey = in_array($apiMethod, self::ENTITY_METHODS) ? 'SORT' : $defaultOrderKey; $params = [ $orderKey => $this->getReverseOrder($order), diff --git a/src/Services/Entity/EntityServiceBuilder.php b/src/Services/Entity/EntityServiceBuilder.php index 2bb6d5d3..bef4c1b3 100644 --- a/src/Services/Entity/EntityServiceBuilder.php +++ b/src/Services/Entity/EntityServiceBuilder.php @@ -46,4 +46,34 @@ public function item(): Entity\Item\Service\Item return $this->serviceCache[__METHOD__]; } -} \ No newline at end of file + + public function section(): Entity\Section\Service\Section + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Entity\Section\Service\Section( + new Entity\Section\Service\Batch($this->batch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + + public function itemProperty(): Entity\Item\Property\Service\Property + { + if (!isset($this->serviceCache[__METHOD__])) { + $batch = new Entity\Item\Property\Batch( + $this->core, + $this->log + ); + $this->serviceCache[__METHOD__] = new Entity\Item\Property\Service\Property( + new Entity\Item\Property\Service\Batch($batch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/Entity/Item/Property/Batch.php b/src/Services/Entity/Item/Property/Batch.php new file mode 100644 index 00000000..c4935120 --- /dev/null +++ b/src/Services/Entity/Item/Property/Batch.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Item\Property; + +use Bitrix24\SDK\Core\Commands\Command; +use Bitrix24\SDK\Core\Commands\CommandCollection; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException; +use Bitrix24\SDK\Core\Response\DTO\Pagination; +use Bitrix24\SDK\Core\Response\DTO\ResponseData; +use Bitrix24\SDK\Core\Response\DTO\Result; +use Bitrix24\SDK\Core\Response\DTO\Time; +use Bitrix24\SDK\Core\Response\Response; +use Generator; +use Psr\Log\LoggerInterface; + +/** + * Class Batch + * + * @package Bitrix24\SDK\Services\Entity\Item\Property + */ +class Batch extends \Bitrix24\SDK\Core\Batch +{ + /** + * Gets property items with batch call + * + * + * @return Generator|ResponseData[] + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + public function getPropertyList( + string $apiMethod, + string $entity, + array $properyCodes + ): Generator { + $this->logger->debug( + 'getProperyList.start', + [ + 'apiMethod' => $apiMethod, + 'entity' => $entity, + 'properyCodes' => $properyCodes, + ] + ); + + try { + $this->clearCommands(); + foreach ($properyCodes as $cnt => $code) { + if (!is_string($code)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of property code «%s» at position %s, the code must be string type', + gettype($code), + $code, + $cnt + ) + ); + } + + $parameters = [ + 'ENTITY' => $entity, + 'PROPERTY' => $code + ]; + $this->registerCommand($apiMethod, $parameters); + } + + foreach ($this->getTraversable(true) as $cnt => $propertyItemResult) { + yield $cnt => $propertyItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch get property items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch get property items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('getProperyList.finish'); + } + + /** + * Delete property items with batch call + * + * + * @return Generator|ResponseData[] + * @throws \Bitrix24\SDK\Core\Exceptions\BaseException + */ + public function deleteEntityItems( + string $apiMethod, + array $entityItemId, + ?array $additionalParameters = null + ): Generator { + $this->logger->debug( + 'deleteEntityItems.start', + [ + 'apiMethod' => $apiMethod, + 'entityItems' => $entityItemId, + 'additionalParameters' => $additionalParameters, + ] + ); + + try { + $this->clearCommands(); + foreach ($entityItemId as $cnt => $itemId) { + if (!is_string($itemId)) { + throw new InvalidArgumentException( + sprintf( + 'invalid type «%s» of property code «%s» at position %s, the code must be string type', + gettype($itemId), + $itemId, + $cnt + ) + ); + } + + $parameters = ['PROPERTY' => $itemId]; + $parameters = array_merge($parameters, $additionalParameters); + + $this->registerCommand($apiMethod, $parameters); + } + + foreach ($this->getTraversable(true) as $cnt => $deletedItemResult) { + yield $cnt => $deletedItemResult; + } + } catch (InvalidArgumentException $exception) { + $errorMessage = sprintf('batch delete property items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + throw $exception; + } catch (\Throwable $exception) { + $errorMessage = sprintf('batch delete property items: %s', $exception->getMessage()); + $this->logger->error( + $errorMessage, + [ + 'trace' => $exception->getTrace(), + ] + ); + + throw new BaseException($errorMessage, $exception->getCode(), $exception); + } + + $this->logger->debug('deleteEntityItems.finish'); + } +} diff --git a/src/Services/Entity/Item/Property/Result/PropertiesResult.php b/src/Services/Entity/Item/Property/Result/PropertiesResult.php new file mode 100644 index 00000000..ba85c9fe --- /dev/null +++ b/src/Services/Entity/Item/Property/Result/PropertiesResult.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Item\Property\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class PropertiesResult extends AbstractResult +{ + /** + * @return PropertyItemResult[] + * @throws BaseException + */ + public function getProperties(): array + { + $items = []; + $res = $this->getCoreResponse()->getResponseData()->getResult(); + if (is_int(key($res))) { + // It was a call without PROPERTY arrtibute + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $items[] = new PropertyItemResult($item); + } + } + elseif (count($res) !== 0) { + // It was a call for one property only + $items[] = new PropertyItemResult($res); + } + + return $items; + } +} diff --git a/src/Services/Entity/Item/Property/Result/PropertyItemResult.php b/src/Services/Entity/Item/Property/Result/PropertyItemResult.php new file mode 100644 index 00000000..51a5a32e --- /dev/null +++ b/src/Services/Entity/Item/Property/Result/PropertyItemResult.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Item\Property\Result; + +use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem; + +/** + * @property-read string $ENTITY + * @property-read string $PROPERTY + * @property-read string $NAME + * @property-read string $TYPE + */ +class PropertyItemResult extends AbstractCrmItem +{ +} diff --git a/src/Services/Entity/Item/Property/Service/Batch.php b/src/Services/Entity/Item/Property/Service/Batch.php new file mode 100644 index 00000000..e6ca21cd --- /dev/null +++ b/src/Services/Entity/Item/Property/Service/Batch.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Item\Property\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +//use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; +use Bitrix24\SDK\Services\Entity\Item\Property\Result\PropertyItemResult; +use Bitrix24\SDK\Services\Entity\Item\Property\Batch as PropertyBatch; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['entity']))] +readonly class Batch +{ + public function __construct( + protected PropertyBatch $batch, + protected LoggerInterface $log + ) { + } + + #[ApiBatchMethodMetadata( + 'entity.item.property.get', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-get.html', + 'Retrieve a list of additional properties of storage elements in batch mode' + )] + public function get(string $entity, array $propertyCodes): Generator + { + foreach ( + $this->batch->getPropertyList( + 'entity.item.property.get', + $entity, + $propertyCodes + ) as $key => $value + ) { + yield $key => new PropertyItemResult($value->getResult()); + } + } + + #[ApiBatchMethodMetadata( + 'entity.item.property.add', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-add.html', + 'Add in batch mode a list of additional properties' + )] + public function add(string $entity, array $properties): Generator + { + $items = []; + foreach ($properties as $item) { + $items[] = array_merge( + [ + 'ENTITY' => $entity + ], + $item + ); + } + + foreach ($this->batch->addEntityItems('entity.item.property.add', $items) as $key => $item) { + yield $key => new AddedItemBatchResult($item); + } + } + + #[ApiBatchMethodMetadata( + 'entity.item.property.delete', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-delete.html', + 'Delete in batch mode a list of storage sections' + )] + public function delete(string $entity, array $propertyCodes): Generator + { + foreach ( + $this->batch->deleteEntityItems( + 'entity.item.property.delete', + $propertyCodes, + ['ENTITY' => $entity] + ) as $key => $item + ) { + yield $key => new DeletedItemBatchResult($item); + } + } + + #[ApiBatchMethodMetadata( + 'entity.item.property.update', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-update.html', + 'Update in batch mode a list of additional properties' + )] + public function update(string $entity, array $items): Generator + { + $dataForUpdate = []; + foreach ($items as $item) { + unset($item['ENTITY']); + $dataForUpdate[] = array_merge( + [ + 'ENTITY' => $entity + ], + $item + ); + } + + foreach ($this->batch->updateEntityItems('entity.item.property.update', $dataForUpdate) as $key => $item) { + yield $key => new DeletedItemBatchResult($item); + } + } +} diff --git a/src/Services/Entity/Item/Property/Service/Property.php b/src/Services/Entity/Item/Property/Service/Property.php new file mode 100644 index 00000000..d7866405 --- /dev/null +++ b/src/Services/Entity/Item/Property/Service/Property.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Item\Property\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\DeletedItemResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Entity\Item\Property\Result\PropertiesResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['entity']))] +class Property extends AbstractService +{ + public function __construct( + public Batch $batch, + CoreInterface $core, + LoggerInterface $logger + ) { + parent::__construct($core, $logger); + } + + /** + * Add an additional property to storage elements. + * + * @see https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-add.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.item.property.add', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-add.html', + 'Add an additional property to storage elements.' + )] + public function add(string $entity, string $propertyCode, string $name, string $type): UpdatedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + $this->guardNonEmptyString($propertyCode, 'entity must be an non empty string'); + $this->guardNonEmptyString($name, 'section name must be an non empty string'); + $this->guardNonEmptyString($type, 'entity must be an non empty string'); + + return new UpdatedItemResult( + $this->core->call( + 'entity.item.property.add', + [ + 'ENTITY' => $entity, + 'PROPERTY' => $propertyCode, + 'NAME' => $name, + 'TYPE' => $type, + ] + ) + ); + } + + /** + * Retrieve a list of additional properties of storage elements. + * + * @see https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-get.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.item.property.get', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-get.html', + 'Retrieve a list of additional properties of storage elements.' + )] + public function get(string $entity, string $propertyCode=''): PropertiesResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + $param = [ + 'ENTITY' => $entity, + ]; + if ($propertyCode !== '') { + $param['PROPERTY'] = $propertyCode; + } + return new PropertiesResult( + $this->core->call( + 'entity.item.property.get', + $param + ) + ); + } + + /** + * Delete an additional property of storage elements. + * + * @see https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-delete.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.item.property.delete', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-delete.html', + 'Delete an additional property of storage elements.' + )] + public function delete(string $entity, string $propertyCode): DeletedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + $this->guardNonEmptyString($propertyCode, 'propery code must be an non empty string'); + + return new DeletedItemResult( + $this->core->call( + 'entity.item.property.delete', + [ + 'ENTITY' => $entity, + 'PROPERTY' => $propertyCode, + ], + ) + ); + } + + /** + * Update an additional property of storage elements. + * + * @see https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-update.html + * + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.item.property.update', + 'https://apidocs.bitrix24.com/api-reference/entity/items/properties/entity-item-property-update.html', + 'Update an additional property of storage elements.' + )] + public function update(string $entity, string $propertyCode, array $fields): UpdatedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + $this->guardNonEmptyString($propertyCode, 'property code must be an non empty string'); + + return new UpdatedItemResult( + $this->core->call( + 'entity.item.property.update', + array_merge( + [ + 'ENTITY' => $entity, + 'PROPERTY' => $propertyCode, + ], + $fields + ), + ) + ); + } +} diff --git a/src/Services/Entity/Section/Result/SectionItemResult.php b/src/Services/Entity/Section/Result/SectionItemResult.php new file mode 100644 index 00000000..8849aded --- /dev/null +++ b/src/Services/Entity/Section/Result/SectionItemResult.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Section\Result; + +use Bitrix24\SDK\Services\CRM\Common\Result\AbstractCrmItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $ID + * @property-read string $ENTITY + * @property-read string $NAME + * @property-read string|null $DESCRIPTION + * @property-read bool $ACTIVE + * @property-read int $SORT + * @property-read array|null $PICTURE + * @property-read array|null $DETAIL_PICTURE + * @property-read int|null $SECTION + */ +class SectionItemResult extends AbstractCrmItem +{ +} diff --git a/src/Services/Entity/Section/Result/SectionsResult.php b/src/Services/Entity/Section/Result/SectionsResult.php new file mode 100644 index 00000000..d32d190c --- /dev/null +++ b/src/Services/Entity/Section/Result/SectionsResult.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Section\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SectionsResult extends AbstractResult +{ + /** + * @return SectionItemResult[] + * @throws BaseException + */ + public function getSections(): array + { + $res = []; + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $item) { + $res[] = new SectionItemResult($item); + } + + return $res; + } +} diff --git a/src/Services/Entity/Section/Service/Batch.php b/src/Services/Entity/Section/Service/Batch.php new file mode 100644 index 00000000..aa1d34fd --- /dev/null +++ b/src/Services/Entity/Section/Service/Batch.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Section\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Result\AddedItemBatchResult; +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; +use Bitrix24\SDK\Services\Entity\Section\Result\SectionItemResult; +use Generator; +use Psr\Log\LoggerInterface; + +#[ApiBatchServiceMetadata(new Scope(['entity']))] +readonly class Batch +{ + public function __construct( + protected BatchOperationsInterface $batch, + protected LoggerInterface $log + ) { + } + + #[ApiBatchMethodMetadata( + 'entity.section.get', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-get.html', + 'Get the list of storage sections in batch mode' + )] + public function get(string $entity, array $sort = [], array $filter = [], int $limit = null): Generator + { + foreach ( + $this->batch->getTraversableList( + 'entity.section.get', + $sort, + $filter, + [], + $limit, + ['ENTITY' => $entity] + ) as $key => $value + ) { + yield $key => new SectionItemResult($value); + } + } + + #[ApiBatchMethodMetadata( + 'entity.section.add', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-add.html', + 'Add in batch mode a list of storage sections' + )] + public function add(string $entity, array $items): Generator + { + $sections = []; + foreach ($items as $item) { + $sections[] = array_merge( + [ + 'ENTITY' => $entity + ], + $item + ); + } + + foreach ($this->batch->addEntityItems('entity.section.add', $sections) as $key => $item) { + yield $key => new AddedItemBatchResult($item); + } + } + + #[ApiBatchMethodMetadata( + 'entity.section.delete', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-delete.html', + 'Delete in batch mode a list of storage sections' + )] + public function delete(string $entity, array $itemIds): Generator + { + foreach ( + $this->batch->deleteEntityItems( + 'entity.section.delete', + $itemIds, + ['ENTITY' => $entity] + ) as $key => $item + ) { + yield $key => new DeletedItemBatchResult($item); + } + } + + #[ApiBatchMethodMetadata( + 'entity.section.update', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-update.html', + 'Update in batch mode a list of storage sections' + )] + public function update(string $entity, array $items): Generator + { + $dataForUpdate = []; + foreach ($items as $item) { + unset($item['ENTITY']); + $dataForUpdate[] = array_merge( + [ + 'ENTITY' => $entity + ], + $item + ); + } + + foreach ($this->batch->updateEntityItems('entity.section.update', $dataForUpdate) as $key => $item) { + yield $key => new DeletedItemBatchResult($item); + } + } +} diff --git a/src/Services/Entity/Section/Service/Section.php b/src/Services/Entity/Section/Service/Section.php new file mode 100644 index 00000000..4b52c611 --- /dev/null +++ b/src/Services/Entity/Section/Service/Section.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Entity\Section\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\AddedItemResult; +use Bitrix24\SDK\Core\Result\DeletedItemResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Entity\Section\Result\SectionsResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['entity']))] +class Section extends AbstractService +{ + public function __construct( + public Batch $batch, + CoreInterface $core, + LoggerInterface $logger + ) { + parent::__construct($core, $logger); + } + + /** + * Adds a storage section + * + * @see https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-add.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.section.add', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-add.html', + 'Adds a storage section' + )] + public function add(string $entity, string $name, array $fields): AddedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + $this->guardNonEmptyString($name, 'section name must be an non empty string'); + + return new AddedItemResult( + $this->core->call( + 'entity.section.add', + array_merge( + [ + 'ENTITY' => $entity, + 'NAME' => $name, + ], + $fields + ), + ) + ); + } + + /** + * Retrieves a list of storage sections + * + * @see https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-get.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.section.get', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-get.html', + 'Retrieves a list of storage sections' + )] + public function get(string $entity, array $sort = [], array $filter = [], int $startItem = 0): SectionsResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + + return new SectionsResult( + $this->core->call( + 'entity.section.get', + [ + 'ENTITY' => $entity, + 'SORT' => $sort, + 'FILTER' => $filter, + 'START' => $startItem, + ], + ) + ); + } + + /** + * Deletes a storage section + * + * @see https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-delete.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.section.delete', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-delete.html', + 'Deletes a storage section' + )] + public function delete(string $entity, int $sectionId): DeletedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + + return new DeletedItemResult( + $this->core->call( + 'entity.section.delete', + [ + 'ENTITY' => $entity, + 'ID' => $sectionId, + ], + ) + ); + } + + /** + * Modifies a storage section + * + * @see https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-update.html + * @throws TransportException + * @throws BaseException + */ + #[ApiEndpointMetadata( + 'entity.section.update', + 'https://apidocs.bitrix24.com/api-reference/entity/sections/entity-section-update.html', + 'Modifies a storage section' + )] + public function update(string $entity, int $id, array $fields): UpdatedItemResult + { + $this->guardNonEmptyString($entity, 'entity must be an non empty string'); + return new UpdatedItemResult( + $this->core->call( + 'entity.section.update', + array_merge( + [ + 'ENTITY' => $entity, + 'ID' => $id, + ], + $fields + ), + ) + ); + } +} diff --git a/tests/Integration/Services/Entity/Item/Property/Service/BatchTest.php b/tests/Integration/Services/Entity/Item/Property/Service/BatchTest.php new file mode 100644 index 00000000..333c2b40 --- /dev/null +++ b/tests/Integration/Services/Entity/Item/Property/Service/BatchTest.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\Entity\Item\Property\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Entity\Item\Property\Service\Batch; +use Bitrix24\SDK\Services\Entity\Item\Property\Service\Property; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Uid\Uuid; + +#[CoversClass(Batch::class)] +#[CoversMethod(Batch::class, 'add')] +#[CoversMethod(Batch::class, 'delete')] +#[CoversMethod(Batch::class, 'get')] +#[CoversMethod(Batch::class, 'update')] +class BatchTest extends TestCase +{ + private ServiceBuilder $sb; + + private Property $propertyService; + + private string $entity = ''; + + protected function setUp(): void + { + $this->sb = Fabric::getServiceBuilder(true); + $this->propertyService = $this->sb->getEntityScope()->itemProperty(); + $this->entity = (string)time(); + $this->sb->getEntityScope()->entity()->add($this->entity, 'Test entity', []); + } + + protected function tearDown(): void + { + $this->sb->getEntityScope()->entity()->delete($this->entity); + } + + /** + * @throws TransportException + * @throws BaseException + */ + public function testGet(): void + { + $propertyCount = 60; + $properties = []; + $propCodes = []; + for ($i = 0; $i < $propertyCount; $i++) { + $fields = $this->getPropertyFields($i); + $properties[] = $fields; + $propCodes[] = $fields['PROPERTY']; + } + + $cnt = 0; + foreach ($this->propertyService->batch->add($this->entity, $properties) as $result) { + $cnt++; + } + + $cnt = 0; + foreach ($this->propertyService->batch->get($this->entity, $propCodes) as $item) { + $cnt++; + } + + $this->assertEquals($propertyCount, $cnt); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $propertyCount = 60; + $properties = []; + for ($i = 0; $i < $propertyCount; $i++) { + $properties[] = $this->getPropertyFields($i); + } + + $cnt = 0; + foreach ($this->propertyService->batch->add($this->entity, $properties) as $result) { + $cnt++; + } + + $this->assertEquals($propertyCount, $cnt); + } + + public function testDelete(): void + { + $propertyCount = 60; + $properties = []; + $propCodes = []; + for ($i = 0; $i < $propertyCount; $i++) { + $fields = $this->getPropertyFields($i); + $properties[] = $fields; + $propCodes[] = $fields['PROPERTY']; + } + + foreach ($this->propertyService->batch->add($this->entity, $properties) as $result) { + } + + foreach ($this->propertyService->batch->delete($this->entity, $propCodes) as $result) { + $this->assertTrue($result->isSuccess()); + } + + $this->assertEquals(0, count($this->propertyService->get($this->entity)->getProperties() )); + } + + public function testUpdate(): void + { + $propertyCount = 60; + $properties = []; + $propNames = []; + $propCodes = []; + for ($i = 0; $i < $propertyCount; $i++) { + $fields = $this->getPropertyFields($i); + $properties[] = $fields; + $propNames[] = [ + 'PROPERTY' => $fields['PROPERTY'], + 'NAME' => $fields['NAME'], + ]; + $propCodes[] = $fields['PROPERTY']; + } + + foreach ($this->propertyService->batch->add($this->entity, $properties) as $result) { + } + + $modifiedNames = []; + foreach ($propNames as $item) { + $modifiedNames[$item['PROPERTY']] = [ + 'PROPERTY' => $item['PROPERTY'], + 'NAME' => 'updated ' . Uuid::v7()->toRfc4122(), + ]; + } + + foreach ($this->propertyService->batch->update($this->entity, array_values($modifiedNames)) as $result) { + $this->assertTrue($result->isSuccess()); + } + + $updatedNames = []; + foreach ($this->propertyService->batch->get($this->entity, $propCodes) as $item) { + $updatedNames[$item->PROPERTY] = $item->NAME; + } + + foreach ($modifiedNames as $code => $fields) { + $this->assertEquals( + $fields['NAME'], + $updatedNames[$code], + ); + } + } + + private function getPropertyFields($num=1) { + return [ + 'PROPERTY' => 'TEST_PROPERTY_' . $num, + 'NAME' => Uuid::v7()->toRfc4122() . $num, + 'TYPE' => 'S', + ]; + } +} diff --git a/tests/Integration/Services/Entity/Item/Property/Service/PropertyTest.php b/tests/Integration/Services/Entity/Item/Property/Service/PropertyTest.php new file mode 100644 index 00000000..9fd5567f --- /dev/null +++ b/tests/Integration/Services/Entity/Item/Property/Service/PropertyTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\Entity\Item\Property\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Entity\Item\Property\Service\Property; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Uid\Uuid; + +#[CoversClass(Property::class)] +#[CoversMethod(Property::class, 'add')] +#[CoversMethod(Property::class, 'get')] +#[CoversMethod(Property::class, 'delete')] +#[CoversMethod(Property::class, 'update')] +class PropertyTest extends TestCase +{ + private ServiceBuilder $sb; + + private Property $propertyService; + + private string $entity = ''; + + protected function setUp(): void + { + $this->sb = Fabric::getServiceBuilder(true); + $this->propertyService = $this->sb->getEntityScope()->itemProperty(); + $this->entity = (string)time(); + $this->sb->getEntityScope()->entity()->add($this->entity, 'Test entity', []); + } + + protected function tearDown(): void + { + $this->sb->getEntityScope()->entity()->delete($this->entity); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $code = 'TEST_PROPERTY'; + $res = $this->addProperty($code); + $this->assertTrue($res->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + $code = 'TEST_PROPERTY'; + $this->addProperty($code); + $properties = $this->propertyService->get($this->entity, $code)->getProperties(); + + $this->assertEquals($code, current(array_column($properties, 'PROPERTY'))); + } + + public function testDelete(): void + { + $code = 'TEST_PROPERTY'; + $this->addProperty($code); + + $this->assertTrue($this->propertyService->delete($this->entity, $code)->isSuccess()); + } + + public function testUpdate(): void + { + $code = 'TEST_PROPERTY';; + $this->addProperty($code); + $properties = $this->propertyService->get($this->entity, $code)->getProperties(); + + $this->assertEquals($code, current(array_column($properties, 'PROPERTY'))); + + $newName = Uuid::v7()->toRfc4122(); + $this->assertTrue($this->propertyService->update( + $this->entity,$code, ['NAME'=>$newName] + )->isSuccess()); + + $properties = $this->propertyService->get($this->entity, $code)->getProperties(); + $this->assertEquals($newName, current(array_column($properties, 'NAME'))); + } + + private function addProperty($code) { + $name = Uuid::v7()->toRfc4122(); + $type = 'S'; + + return $this->propertyService->add($this->entity, $code, $name, $type); + } +} diff --git a/tests/Integration/Services/Entity/Section/Service/BatchTest.php b/tests/Integration/Services/Entity/Section/Service/BatchTest.php new file mode 100644 index 00000000..38ccb478 --- /dev/null +++ b/tests/Integration/Services/Entity/Section/Service/BatchTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\Entity\Section\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Entity\Section\Service\Batch; +use Bitrix24\SDK\Services\Entity\Section\Service\Section; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Uid\Uuid; + +#[CoversClass(Batch::class)] +#[CoversMethod(Batch::class, 'add')] +#[CoversMethod(Batch::class, 'delete')] +#[CoversMethod(Batch::class, 'get')] +#[CoversMethod(Batch::class, 'update')] +class BatchTest extends TestCase +{ + private ServiceBuilder $sb; + + private Section $sectionService; + + private string $entity = ''; + + protected function setUp(): void + { + $this->sb = Fabric::getServiceBuilder(true); + $this->sectionService = $this->sb->getEntityScope()->section(); + $this->entity = (string)time(); + $this->sb->getEntityScope()->entity()->add($this->entity, 'Test entity', []); + } + + protected function tearDown(): void + { + $this->sb->getEntityScope()->entity()->delete($this->entity); + } + + /** + * @throws TransportException + * @throws BaseException + */ + public function testGet(): void + { + $sectionsCount = 60; + $sections = []; + for ($i = 0; $i < $sectionsCount; $i++) { + $sections[] = [ + 'NAME' => 'name ' . Uuid::v7()->toRfc4122(), + ]; + } + + $addedSectionIds = []; + foreach ($this->sectionService->batch->add($this->entity, $sections) as $result) { + $addedSectionIds[] = $result->getId(); + } + + $this->assertCount($sectionsCount, $addedSectionIds); + + $resultSections = []; + foreach ($this->sectionService->batch->get($this->entity, [], []) as $item) { + $resultSections[$item->ID] = $item; + } + + foreach ($addedSectionIds as $addedSectionId) { + $this->assertArrayHasKey($addedSectionId, $resultSections); + $this->assertEquals($addedSectionId, $resultSections[$addedSectionId]->ID); + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $sectionsCount = 60; + $sections = []; + for ($i = 0; $i < $sectionsCount; $i++) { + $sections[] = [ + 'NAME' => 'name ' . Uuid::v7()->toRfc4122(), + ]; + } + + $addedSectionIds = []; + foreach ($this->sectionService->batch->add($this->entity, $sections) as $result) { + $addedSectionIds[] = $result->getId(); + } + + $this->assertCount($sectionsCount, $addedSectionIds); + } + + public function testDelete(): void + { + $sectionsCount = 60; + $sections = []; + for ($i = 0; $i < $sectionsCount; $i++) { + $sections[] = [ + 'NAME' => 'name ' . Uuid::v7()->toRfc4122(), + ]; + } + + $addedSectionIds = []; + foreach ($this->sectionService->batch->add($this->entity, $sections) as $result) { + $addedSectionIds[] = $result->getId(); + } + + $this->assertCount($sectionsCount, $addedSectionIds); + + foreach ($this->sectionService->batch->delete($this->entity, $addedSectionIds) as $result) { + $this->assertTrue($result->isSuccess()); + } + + $this->assertEquals([], $this->sectionService->get($this->entity, [], ['ID' => $addedSectionIds])->getSections()); + } + + public function testUpdate(): void + { + $sectionsCount = 60; + $sections = []; + for ($i = 0; $i < $sectionsCount; $i++) { + $sections[] = [ + 'NAME' => 'name ' . Uuid::v7()->toRfc4122(), + ]; + } + + $addedSectionIds = []; + foreach ($this->sectionService->batch->add($this->entity, $sections) as $result) { + $addedSectionIds[] = $result->getId(); + } + + $this->assertCount($sectionsCount, $addedSectionIds); + + $resultSections = []; + foreach ($this->sectionService->batch->get($this->entity, [], []) as $item) { + $resultSections[] = $item; + } + + $modifiedSections = []; + foreach ($resultSections as $item) { + $modifiedSections[$item->ID] = [ + 'ID' => $item->ID, + 'NAME' => 'updated name ' . Uuid::v7()->toRfc4122(), + ]; + } + + // batch update elements + foreach ($this->sectionService->batch->update($this->entity, array_values($modifiedSections)) as $result) { + $this->assertTrue($result->isSuccess()); + } + + // read elements in batch mode + $updatedElements = []; + foreach ($this->sectionService->batch->get($this->entity, [], []) as $item) { + $updatedElements[$item->ID] = $item; + } + + foreach ($modifiedSections as $id => $fields) { + $this->assertEquals( + $fields['NAME'], + $updatedElements[$id]->NAME, + ); + } + } +} diff --git a/tests/Integration/Services/Entity/Section/Service/SectionTest.php b/tests/Integration/Services/Entity/Section/Service/SectionTest.php new file mode 100644 index 00000000..de007aea --- /dev/null +++ b/tests/Integration/Services/Entity/Section/Service/SectionTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\Entity\Section\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Entity\Section\Service\Section; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Uid\Uuid; + +#[CoversClass(Section::class)] +#[CoversMethod(Section::class, 'add')] +#[CoversMethod(Section::class, 'get')] +#[CoversMethod(Section::class, 'delete')] +#[CoversMethod(Section::class, 'update')] +class SectionTest extends TestCase +{ + private ServiceBuilder $sb; + + private Section $sectionService; + + private string $entity = ''; + + protected function setUp(): void + { + $this->sb = Fabric::getServiceBuilder(true); + $this->sectionService = $this->sb->getEntityScope()->section(); + $this->entity = (string)time(); + $this->sb->getEntityScope()->entity()->add($this->entity, 'Test entity', []); + } + + protected function tearDown(): void + { + $this->sb->getEntityScope()->entity()->delete($this->entity); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAdd(): void + { + $name = Uuid::v7()->toRfc4122(); + $sectionId = $this->sectionService->add($this->entity, $name, [])->getId(); + $sections = $this->sectionService->get($this->entity, [], ['ID' => $sectionId])->getSections(); + $this->assertEquals($sectionId, current(array_column($sections, 'ID'))); + $this->assertEquals($name, current(array_column($sections, 'NAME'))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGet(): void + { + $name = Uuid::v7()->toRfc4122(); + $sectionId = $this->sectionService->add($this->entity, $name, [])->getId(); + $sections = $this->sectionService->get($this->entity, [], ['ID' => $sectionId])->getSections(); + + $this->assertEquals($sectionId, current(array_column($sections, 'ID'))); + } + + public function testDelete(): void + { + $name = Uuid::v7()->toRfc4122(); + $sectionId = $this->sectionService->add($this->entity, $name, [])->getId(); + + $this->assertTrue($this->sectionService->delete($this->entity, $sectionId)->isSuccess()); + $this->assertEquals([], $this->sectionService->get($this->entity, [], ['ID' => $sectionId])->getSections()); + } + + public function testUpdate(): void + { + $name = Uuid::v7()->toRfc4122(); + $sectionId = $this->sectionService->add($this->entity, $name, [])->getId(); + $sections = $this->sectionService->get($this->entity, [], ['ID' => $sectionId])->getSections(); + + $this->assertEquals($sectionId, current(array_column($sections, 'ID'))); + + $newName = Uuid::v7()->toRfc4122(); + $this->assertTrue($this->sectionService->update( + $this->entity,$sectionId, ['NAME'=>$newName] + )->isSuccess()); + + $sections = $this->sectionService->get($this->entity, [], ['ID' => $sectionId])->getSections(); + $this->assertEquals($newName, current(array_column($sections, 'NAME'))); + } +}