Working with value objects for ids in Symfony
Installs: 26 244
Dependents: 3
Suggesters: 0
Security: 0
Stars: 5
Watchers: 0
Forks: 6
Open Issues: 2
Type:symfony-bundle
Requires
- php: 8.2.*|8.3.*
- doctrine/dbal: ^2.13.8|^3.3.6
- symfony/framework-bundle: ^6.3|^7.0
- symfony/polyfill-uuid: ^1.26
- symfony/serializer: ^6.3|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^v3.59.3
- infection/infection: 0.27.*
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^10.5
- vimeo/psalm: ^5.25
Suggests
- ext-uuid: Improves performance of id creation and validation. symfony/polyfill-uuid is used as a fallback.
- dev-main
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.0
- v0.1.0
- v0.1.0-beta.7
- v0.1.0-beta.6
- v0.1.0-beta.5
- v0.1.0-beta.4
- v0.1.0-beta.3
- v0.1.0-beta.2
- v0.1.0-beta.1
- v0.1.0-alpha.8
- v0.1.0-alpha.7
- v0.1.0-alpha.6
- v0.1.0-alpha.5
- v0.1.0-alpha.4
- v0.1.0-alpha.3
- v0.1.0-alpha.2
- v0.1.0-alpha.1
- dev-php-8-4
- dev-dependabot/composer/symfony/process-7.1.7
- dev-dependabot/composer/symfony/http-foundation-7.1.7
This package is auto-updated.
Last update: 2024-11-06 20:33:06 UTC
README
A Symfony bundle to work with id and id list value objects in Symfony. It includes Symfony normalizers for automatic normalization and denormalization and Doctrine types to store the ids and id lists directly in the database.
As it's a central part of an application, it's tested thoroughly (including mutation testing).
Installation and configuration
Install package through composer:
composer require digital-craftsman/ids
It's recommended that you install the uuid
PHP extension for better performance of id creation and validation. symfony/polyfill-uuid
is used as a fallback. You can prevent installing the polyfill when you've installed the PHP extension.
Working with ids
Creating a new id
The bulk of the logic is in the Id
class. Creating a new id is as simple as creating a new final readonly class
and extending from it like the following:
<?php declare(strict_types=1); namespace App\ValueObject; use DigitalCraftsman\Ids\ValueObject\Id; final readonly class UserId extends Id { }
Now you're already able to use it in your code like this:
$userId = UserId::generateRandom();
if ($userId->isEqualTo($command->userId)) { ... }
Guard against invalid usages:
$requestingUser->userId->mustNotBeEqualTo($command->targetUserId);
Or with a custom exception:
$requestingUser->userId->mustNotBeEqualTo( $command->targetUserId, static fn () => new Exception\UserCanNotTargetItself(), );
Symfony serializer
If you're injecting the SerializerInterface
directly, there is nothing to do. The normalizer for the id is automatically registered.
namespace App\DTO; final readonly class UserPayload { public function __construct( public UserId $userId, public string $firstName, public string $lastName, ) { } }
public function __construct( private SerializerInterface $serializer, ) { } public function handle(UserPayload $userPayload): string { return $this->serializer->serialize($userPayload, JsonEncoder::FORMAT); }
{ "userId": "15d6208b-7cf2-49e5-a193-301d594d98a7", "firstName": "Tomas", "lastName": "Bauer" }
This can be combined with the CQRS bundle to have serialized ids there.
Doctrine types
To use an id in your entities, you just need to register a new type for the id. Create a new class for the new id like the following:
<?php declare(strict_types=1); namespace App\Doctrine; use App\ValueObject\UserId; use DigitalCraftsman\Ids\Doctrine\IdType; final class UserIdType extends IdType { public static function getTypeName(): string { return 'user_id'; } public static function getClass(): string { return UserId::class; } }
Then register the new type in your config/packages/doctrine.yaml
file:
doctrine: dbal: types: user_id: App\Doctrine\UserIdType
Alternatively you can also add a compiler pass to register the types automatically.
Then you're already able to add it into your entity like this:
<?php declare(strict_types=1); namespace App\Entity; use App\ValueObject\UserId; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Table(name: '`user`')] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\Column(name: 'id', type: 'user_id')] public UserId $id; ... }
Working with id lists
Id lists are wrapper for an array of ids. They contain a few utility functions and improved type safety.
The IdList
is immutable. Therefore, the mutation methods (like add
, remove
, ...) always return a new instance of the list.
Creating a new id list
The bulk of the logic is in the IdList
class. Creating a new id list is as simple as creating a new final readonly class
and extending from it like the following:
<?php declare(strict_types=1); namespace App\ValueObject; use DigitalCraftsman\Ids\ValueObject\IdList; /** @extends IdList<UserId> */ final readonly class UserIdList extends IdLIst { public static function handlesIdClass(): string { return UserId::class; } }
Now you're already able to use it in your code like this:
$userIdList = new UserIdList($userIds);
if ($idsOfEnabledUsers->contains($command->userId)) { ... }
Guard against invalid usages:
$idsOfEnabledUsers->mustContainId($command->targetUserId);
Or with custom exception:
$idsOfEnabledUsers->mustContainId( $command->targetUserId, static fn () => new Exception\UserIsNotEnabled(), );
Symfony serializer
If you're injecting the SerializerInterface
directly, there is nothing to do. The normalizer for the id list is automatically registered.
Doctrine types
To use an id list in your entities, you just need to register a new type for the id list. Create a new class for the new id list like the following:
<?php declare(strict_types=1); namespace App\Doctrine; use App\ValueObject\UserId; use App\ValueObject\UserIdList; use DigitalCraftsman\Ids\Doctrine\IdListType; final class UserIdListType extends IdListType { protected function getTypeName(): string { return 'user_id_list'; } protected function getIdListClass(): string { return UserIdList::class; } protected function getIdClass(): string { return UserId::class; } }
Then register the new type in your config/packages/doctrine.yaml
file:
doctrine: dbal: types: user_id_list: App\Doctrine\UserIdListType
Then you're already able to add it into your entity like this:
<?php declare(strict_types=1); namespace App\Entity; use App\ValueObject\UserIdList; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: InvestorRepository::class)] #[ORM\Table(name: 'investor')] class Investor { #[ORM\Column(name: 'ids_of_users_with_access', type: 'user_id_list')] public UserIdList $idsOfUsersWithAccess; ... }