Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active July 25, 2020 08:09
Show Gist options
  • Save webdevilopers/93b82fedd25a9d43d2af64de2fa0c57f to your computer and use it in GitHub Desktop.
Save webdevilopers/93b82fedd25a9d43d2af64de2fa0c57f to your computer and use it in GitHub Desktop.
How not allow extra fields in Command DTO using Symfony Messenger
<?php
use Webmozart\Assert\Assert;
final class Address
{
/** @var string|null $street */
private $street;
/** @var Postcode|null $postcode */
private $postcode;
/** @var string|null $city */
private $city;
/** @var CountryCode $countryCode */
private $countryCode;
private function __construct(?string $aStreet, ?Postcode $aPostcode, ?string $aCity, CountryCode $aCountryCode)
{
Assert::nullOrNotEmpty($aStreet);
Assert::nullOrNotEmpty($aCity);
$this->street = $aStreet;
$this->postcode = $aPostcode;
$this->city = $aCity;
$this->countryCode = $aCountryCode;
}
public static function fromArray(array $array): Address
{
Assert::keyExists($array, 'street');
Assert::nullOrString($array['street']);
Assert::keyExists($array, 'postcode');
Assert::nullOrString($array['postcode']);
Assert::keyExists($array, 'city');
Assert::nullOrString($array['city']);
Assert::keyExists($array, 'countryCode');
Assert::nullOrString($array['countryCode']);
return new self(
$array['street'],
null !== $array['postcode'] ? Postcode::fromString($array['postcode']) : null,
$array['city'],
null !== $array['countryCode'] ? CountryCode::fromString($array['countryCode']) : null
);
}
public function toArray(): array
{
return [
'street' => $this->street,
'postcode' => null !== $this->postcode ? $this->postcode->toString() : null,
'city' => $this->city,
'countryCode' => null !== $this->countryCode ? $this->countryCode->toString() : null
];
}
public function street(): ?string
{
return $this->street;
}
public function postcode(): ?Postcode
{
return $this->postcode;
}
public function city(): ?string
{
return $this->city;
}
public function countryCode(): CountryCode
{
return $this->countryCode;
}
}
<?php
final class AgencyController
{
/** @var MessageBusInterface */
private $commandBus;
public function hireAction(Request $request): Response
{
$command = new HireAgency(json_decode($request->getContent(), true));
$this->commandBus->dispatch($command);
return new JsonResponse(null, Response::HTTP_CREATED);
}
}
<?php
use Symfony\Component\Validator\Constraints as Assert;
final class HireAgency
{
/**
* @var AgencyId
* @Assert\NotNull
* @Assert\Uuid
*/
private $agencyId;
/**
* @var string
* @Assert\NotNull
* @Assert\NotBlank
*/
private $name;
/**
* @var ValueAddedTaxIdentificationNumber
* @Assert\NotNull
* @Assert\NotBlank
*/
private $vatId;
/**
* @var string
* @Assert\Type(type="string")
* @Assert\NotNull
* @Assert\NotBlank
*/
private $contactPerson;
/**
* @var ContactInformation
* @Assert\NotNull
* @Assert\Type(type="array")
* @Assert\Collection(
* fields = {
* "email" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Email,
* },
* "phone" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* "mobile" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* "fax" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* }, allowExtraFields=false
* )
*/
private $contactInformation;
/**
* @var Address
* @Assert\NotNull
* @Assert\Type(type="array")
* @Assert\Collection(
* fields = {
* "street" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string"),
* },
* "postcode" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* "city" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* "countryCode" = {
* @Assert\NotBlank(allowNull=true),
* @Assert\Type(type="string")
* },
* }, allowExtraFields=false
* )
*/
private $address;
public function __construct(array $payload)
{
$this->agencyId = $payload['agencyId'];
$this->name = $payload['name'];
$this->vatId = $payload['vatId'];
$this->contactPerson = $payload['contactPerson'];
$this->contactInformation = $payload['contactInformation'];
$this->address = $payload['address'];
}
public function agencyId(): AgencyId
{
return AgencyId::fromString($this->agencyId);
}
public function name(): string
{
return $this->name;
}
public function vatId(): ValueAddedTaxIdentificationNumber
{
return ValueAddedTaxIdentificationNumber::fromString($this->vatId);
}
public function contactPerson(): string
{
return $this->contactPerson;
}
public function contactInformation(): ContactInformation
{
return ContactInformation::fromArray($this->contactInformation);
}
public function address(): Address
{
return Address::fromArray($this->address);
}
}
@webdevilopers
Copy link
Author

@weaverryan That makes sense. We used to structure commands differently before. We had an additional "payload" attribute that could be validated by the Collection constraint. But we wanted to simplify our DTOs. In the end you are right that this is not crucial to the actual application and maybe "over-engineering".

Thank you for your feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment