Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ba89b89
Add ApplicationSettings bounded context for issue #67
claude Nov 21, 2025
73d9662
Refactor ApplicationSettings for multi-scope support (issue #67)
claude Nov 21, 2025
52211f9
Refactor ApplicationSettings: Add tracking fields and event system (i…
claude Nov 21, 2025
e76e82c
Add soft-delete support and OnApplicationDelete UseCase (issue #67)
claude Nov 21, 2025
4cf3494
Fix: Add backward compatibility methods to Repository
claude Nov 21, 2025
704c22d
Add getEvents() method to AggregateRoot for testing
claude Nov 21, 2025
ea01d31
Align event handling with existing patterns
claude Nov 21, 2025
4f25ed8
Fix linter errors and move documentation
claude Nov 21, 2025
c656e51
Refactor ApplicationSettings: cleanup and improve architecture
claude Nov 21, 2025
4969132
Final refactoring: simplify Delete UseCase and add comprehensive tests
claude Nov 21, 2025
02e878e
Apply Rector and PHP-CS-Fixer code improvements
claude Nov 21, 2025
fc72254
Fix PHPStan errors in InstallSettingsTest
claude Nov 21, 2025
93bf656
Remove redundant repository methods and simplify API
claude Nov 21, 2025
f855474
Refactor ApplicationSettings: simplify API and add SettingsFetcher se…
claude Nov 21, 2025
20cffda
Refactor SettingsFetcher: rename getSetting to getItem and add except…
claude Nov 21, 2025
6450f18
Add findAllForInstallationByKey method to optimize SettingsFetcher
claude Nov 21, 2025
b85a052
Refactor ApplicationSettings: rename entity, separate Create/Update, …
claude Nov 21, 2025
92d8b1d
Enhance SettingsFetcher: add logger, rename method, add deserializati…
claude Nov 21, 2025
09ff03d
Apply Rector automatic code improvements
claude Nov 21, 2025
88ea683
Improve exception handling and serialization in ApplicationSettings
claude Nov 21, 2025
9e6ae22
Refactor ApplicationSettingsItem to generate UUID internally
claude Nov 21, 2025
b858438
Update Makefile to use Docker for composer license checker
mesilov Nov 23, 2025
35e7f52
Add TODO comments for SDK interface gaps and refactor Bitrix24 accoun…
mesilov Nov 23, 2025
df4a879
Refactor test variable names for clarity and update lambda typing in …
mesilov Nov 23, 2025
c665594
Refactor TODO comment formatting and lambda syntax in account filtering
mesilov Nov 23, 2025
2399fde
Update license-check workflow to use `composer-license-checker` directly
mesilov Nov 23, 2025
38bbbc3
Translate ApplicationSettings documentation to English, update code e…
mesilov Nov 23, 2025
8d3c5e6
Refactor repository tests: add contract tests for consistency, move i…
mesilov Nov 23, 2025
4e7521f
Update README.md: adjust formatting, add new ApplicationSettings sect…
mesilov Nov 23, 2025
a06a148
Merge remote-tracking branch 'origin/dev' into claude/fix-bitrix24-is…
mesilov Nov 23, 2025
ce211c1
Add PHP 8.4 support in composer requirements and GitHub workflows
mesilov Nov 23, 2025
f187837
Remove outdated TODO comment and redundant PHPStan ignore directive i…
mesilov Nov 23, 2025
6346c29
Refactor ApplicationSettings tests: introduce `flushChanges` method f…
mesilov Nov 23, 2025
50f36ab
Refactor repository methods: add `#[\Override]` annotations and remov…
mesilov Nov 23, 2025
1e7a1e1
Update CLAUDE.md: add steps for running linters and tests after each …
mesilov Nov 23, 2025
218b3f8
Refactor ApplicationSettingsItemRepository: remove EntityRepository i…
mesilov Nov 23, 2025
8ba250e
Replace custom exceptions with SDK standard exceptions for consistenc…
mesilov Nov 24, 2025
aa39517
Rename `InstallSettings` to `DefaultSettingsInstaller` for improved s…
mesilov Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xs="https://www.w3.org/2001/XMLSchema"
xmlns:orm="https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Bitrix24\Lib\ApplicationSettings\Entity\ApplicationSetting"
table="application_setting">
<id name="id" type="uuid" column="id">

</id>

<field name="applicationInstallationId" type="uuid" column="application_installation_id" nullable="false"/>

<field name="key" type="string" column="key" length="255" nullable="false"/>

<field name="value" type="text" column="value" nullable="false"/>

<field name="createdAt" type="carbon_immutable" column="created_at_utc" precision="3" nullable="false"/>

<field name="updatedAt" type="carbon_immutable" column="updated_at_utc" precision="3" nullable="false"/>

<unique-constraints>
<unique-constraint columns="application_installation_id,key" name="unique_app_installation_key"/>
</unique-constraints>

<indexes>
<index name="idx_application_installation_id" columns="application_installation_id"/>
</indexes>
</entity>
</doctrine-mapping>
111 changes: 111 additions & 0 deletions src/ApplicationSettings/Entity/ApplicationSetting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Bitrix24\Lib\ApplicationSettings\Entity;

use Bitrix24\Lib\AggregateRoot;
use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Carbon\CarbonImmutable;
use Symfony\Component\Uid\Uuid;

/**
* Application setting entity
*
* Stores key-value settings for application installations.
* Each ApplicationInstallation can have multiple settings identified by unique keys.
*/
class ApplicationSetting extends AggregateRoot
{
private readonly CarbonImmutable $createdAt;
private CarbonImmutable $updatedAt;
private string $value;

public function __construct(
private readonly Uuid $id,
private readonly Uuid $applicationInstallationId,
private readonly string $key,
string $value
) {
$this->validateKey($key);
$this->validateValue($value);

$this->value = $value;
$this->createdAt = new CarbonImmutable();
$this->updatedAt = new CarbonImmutable();
}

public function getId(): Uuid
{
return $this->id;
}

public function getApplicationInstallationId(): Uuid
{
return $this->applicationInstallationId;
}

public function getKey(): string
{
return $this->key;
}

public function getValue(): string
{
return $this->value;
}

public function getCreatedAt(): CarbonImmutable
{
return $this->createdAt;
}

public function getUpdatedAt(): CarbonImmutable
{
return $this->updatedAt;
}

/**
* Update setting value
*/
public function updateValue(string $value): void
{
$this->validateValue($value);

if ($this->value !== $value) {
$this->value = $value;
$this->updatedAt = new CarbonImmutable();
}
}

/**
* Validate setting key
*/
private function validateKey(string $key): void
{
if ('' === trim($key)) {
throw new InvalidArgumentException('Setting key cannot be empty');
}

if (strlen($key) > 255) {
throw new InvalidArgumentException('Setting key cannot exceed 255 characters');
}

// Key should contain only alphanumeric characters, underscores, dots, and hyphens
if (!preg_match('/^[a-zA-Z0-9_.-]+$/', $key)) {
throw new InvalidArgumentException(
'Setting key can only contain alphanumeric characters, underscores, dots, and hyphens'
);
}
}

/**
* Validate setting value
*/
private function validateValue(string $value): void
{
// Value can be empty but not null (handled by type hint)
// We store value as string, could be JSON or plain text
// No specific validation needed here, can be extended if needed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Bitrix24\Lib\ApplicationSettings\Infrastructure\Doctrine;

use Bitrix24\Lib\ApplicationSettings\Entity\ApplicationSetting;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Uid\Uuid;

/**
* Repository for ApplicationSetting entity
*
* @extends EntityRepository<ApplicationSetting>
*/
class ApplicationSettingRepository extends EntityRepository
{
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager, $entityManager->getClassMetadata(ApplicationSetting::class));
}

/**
* Save application setting
*/
public function save(ApplicationSetting $applicationSetting): void
{
$this->getEntityManager()->persist($applicationSetting);
}

/**
* Delete application setting
*/
public function delete(ApplicationSetting $applicationSetting): void
{
$this->getEntityManager()->remove($applicationSetting);
}

/**
* Find setting by ID
*/
public function findById(Uuid $id): ?ApplicationSetting
{
return $this->getEntityManager()
->getRepository(ApplicationSetting::class)
->createQueryBuilder('s')
->where('s.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}

/**
* Find setting by application installation ID and key
*/
public function findByApplicationInstallationIdAndKey(
Uuid $applicationInstallationId,
string $key
): ?ApplicationSetting {
return $this->getEntityManager()
->getRepository(ApplicationSetting::class)
->createQueryBuilder('s')
->where('s.applicationInstallationId = :applicationInstallationId')
->andWhere('s.key = :key')
->setParameter('applicationInstallationId', $applicationInstallationId)
->setParameter('key', $key)
->getQuery()
->getOneOrNullResult();
}

/**
* Find all settings for application installation
*
* @return ApplicationSetting[]
*/
public function findByApplicationInstallationId(Uuid $applicationInstallationId): array
{
return $this->getEntityManager()
->getRepository(ApplicationSetting::class)
->createQueryBuilder('s')
->where('s.applicationInstallationId = :applicationInstallationId')
->setParameter('applicationInstallationId', $applicationInstallationId)
->orderBy('s.key', 'ASC')
->getQuery()
->getResult();
}

/**
* Delete all settings for application installation
*/
public function deleteByApplicationInstallationId(Uuid $applicationInstallationId): void
{
$this->getEntityManager()
->createQueryBuilder()
->delete(ApplicationSetting::class, 's')
->where('s.applicationInstallationId = :applicationInstallationId')
->setParameter('applicationInstallationId', $applicationInstallationId)
->getQuery()
->execute();
}
}
28 changes: 28 additions & 0 deletions src/ApplicationSettings/UseCase/Delete/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Bitrix24\Lib\ApplicationSettings\UseCase\Delete;

use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Symfony\Component\Uid\Uuid;

/**
* Command to delete application setting
*/
readonly class Command
{
public function __construct(
public Uuid $applicationInstallationId,
public string $key
) {
$this->validate();
}

private function validate(): void
{
if ('' === trim($this->key)) {
throw new InvalidArgumentException('Setting key cannot be empty');
}
}
}
54 changes: 54 additions & 0 deletions src/ApplicationSettings/UseCase/Delete/Handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Bitrix24\Lib\ApplicationSettings\UseCase\Delete;

use Bitrix24\Lib\ApplicationSettings\Infrastructure\Doctrine\ApplicationSettingRepository;
use Bitrix24\Lib\Services\Flusher;
use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Psr\Log\LoggerInterface;

/**
* Handler for Delete command
*/
readonly class Handler
{
public function __construct(
private ApplicationSettingRepository $applicationSettingRepository,
private Flusher $flusher,
private LoggerInterface $logger
) {
}

public function handle(Command $command): void
{
$this->logger->info('ApplicationSettings.Delete.start', [
'applicationInstallationId' => $command->applicationInstallationId->toRfc4122(),
'key' => $command->key,
]);

$setting = $this->applicationSettingRepository->findByApplicationInstallationIdAndKey(
$command->applicationInstallationId,
$command->key
);

if (null === $setting) {
throw new InvalidArgumentException(
sprintf(
'Setting with key "%s" not found for application installation "%s"',
$command->key,
$command->applicationInstallationId->toRfc4122()
)
);
}

$settingId = $setting->getId()->toRfc4122();
$this->applicationSettingRepository->delete($setting);
$this->flusher->flush();

$this->logger->info('ApplicationSettings.Delete.finish', [
'settingId' => $settingId,
]);
}
}
28 changes: 28 additions & 0 deletions src/ApplicationSettings/UseCase/Get/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Bitrix24\Lib\ApplicationSettings\UseCase\Get;

use Bitrix24\SDK\Core\Exceptions\InvalidArgumentException;
use Symfony\Component\Uid\Uuid;

/**
* Command to get application setting
*/
readonly class Command
{
public function __construct(
public Uuid $applicationInstallationId,
public string $key
) {
$this->validate();
}

private function validate(): void
{
if ('' === trim($this->key)) {
throw new InvalidArgumentException('Setting key cannot be empty');
}
}
}
Loading
Loading