Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

webrobert's avatar

Is this where I use a DTO?

Delta. Tango. Oscar.

On my Document/Contract project.. I have about 8 document types (so far). All with different values. I opted to store the values in a values column on the document, no judgments please. I'm a grown up... mostly.

First pass, I made anonymous blade components for each document template and defined the values as props at the top, for sanity.

Now, I'm to the document making part of this journey. I want to choose a document and fill in the values. AND then I started thinking - that may be the issue. This isn't a standard "post" or "user" and I don't want to be chasing value definitions all over the place.

Is this where I use a DTO? Essentially making a template with the required values. Maybe DTOs aren't quite right.

You get me? Elegant solution?

[
  "name" => "Letter of Intent"
  "values" => [
    "price" => 190000,
    "seller_finance_amount" => 9500,
    "interest_rate" => 6,
    "psa_execution_deadline" => "2023-02-27T00:00:00.000000Z",
    "days_for_due_diligence" => 14,
    "days_for_exclusivity" => 30,
    "other_terms" => "the sale includes a) one month of in-store training, b) a non-compete provision suitable for SBA lender",
  ],
]
[
  "name" => "Allocation of purchase price",
  "values" => [
    "price" => 190000,
    "allocation" =>  [
      "inventory" => 5000,
      "accounts_receivable" => null,
      "ffe" => 15000,
      "training_transition" => 8000,
      "non_compete_consideration" => 15000,
      "other" => null,
    ],
  ],
]
[
  "name" => "Contingencies to purchase agreement",
  "values" => [
    "contingencies_deadline" => "2023-02-27T00:00:00.000000Z"
  ],
]
0 likes
6 replies
Sinnbeck's avatar

Are they different for every single document? Or do you have like 3-4 Schemas that repeat?

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@webrobert Then that would mean 8 diffferent types of DTO's. One for each "shape". The idea of a DTO is that you pass in the data, it holds it form (readonly) and you get the same form out again.. And the DTO in your case sounds be one the "values". Hard to say if it makes sense in your context. but it would make sure you can predict the data.

webrobert's avatar

@Sinnbeck,

yeah I made one last night, and it quickly turned into something else... NOT a DTO.

I added a method to the document model, valuesObject(), still a draft so the naming isn't quite right. it will use the document slug to factory up the correct class for values..

$document = new PurchaseSaleDocument(['name' => 'Letter of Intent']),
$document->valuesObject()->valuesMap()

I used reflection to get a list of all the properties and their types,

[
    "price" => "int"
    "seller_finance_amount" => "int"
    "interest_rate" => "int"
    "psa_execution_deadline" => "DateTime"
    "days_for_due_diligence" => "int"
    "days_for_exclusivity" => "int"
    "other_terms" => "string"
]

which I can then loop thru to build the blade form with correct input types

and then when it's time to save the form I can.

    $document->valuesObject()->validate([
  			...
    ]),

And here is the class, again not a Dto still need to correctly name it.

class LetterOfIntentValues extends BaseDto
{
    public int $price;
    public int $seller_finance_amount;
    public int $interest_rate;
    public \DateTime $psa_execution_deadline;
    public int $days_for_due_diligence;
    public int $days_for_exclusivity;
    public string $other_terms;
    
    public function validate($values): self
    {
        $validator = Validator::make($values, [
            'price' => 'required|int',
            'seller_finance_amount' => 'required|int',
            'interest_rate' => 'required|int',
            'psa_execution_deadline' => 'required|date',
            'days_for_due_diligence' => 'required|int',
            'days_for_exclusivity' => 'required|int',
            'other_terms' => 'required|string',
        ])->validateWithBag('letterOfIntentValues');

        collect($validator)->each( fn($value, $key) => $this->{$key} = $value);

        return $this;
    }
}

So all the logic for that contracts values live in the one class and in the blade contract template. What do you think? It's feeling better.

Sinnbeck's avatar

@webrobert I will say that without knowing your project, just looking at the code, I can easily follow it. Its very easy to see the exact shape of the data. So yeah I would say this is a huge improvement.

webrobert's avatar

@Sinnbeck,

It's coming a long. Still not quite ideal. Can't put a finger on it. yet. I did move the validation it now keeps just the rules...

class LetterOfIntent extends ContractValues
{
    public int $price;
    public int $seller_finance_amount;
    public int $interest_rate = 6;
    public \DateTime $psa_execution_deadline;
    public int $days_for_due_diligence = 14;
    public int $days_for_exclusivity = 30;
    public string $other_terms = "the sale includes a) one month of in-store training, b) a non-compete provision suitable for SBA lender";

    public function rules(): array
    {
        return [
            'price' => 'required|int',
            'seller_finance_amount' => 'required|int',
            'interest_rate' => 'required|int',
            'psa_execution_deadline' => 'required|date',
            'days_for_due_diligence' => 'required|int',
            'days_for_exclusivity' => 'required|int',
            'other_terms' => 'required|string',
        ];
    }
}

and then the Create action grabs the rules...

class CreatePurchaseDocument
{
    public function __invoke($transaction, $document): PurchaseSaleDocument
    {
        Validator::make($document->toArray(), [
            'name' => 'required|string|max:255',
            ...$document->values()->rules()
        ])->validateWithBag('CreatePurchaseDocument');

        $document = $transaction->documents()->create([
            'name'   => $document['name'],
            'values' => $document->only($document->values()->keys()),
        ]);

        return $document;
    }
}

The only catch is I did like the dto concept. I considered just adding a method for it..

    public static function dto(
        $price,
        $seller_finance_amount,
        $interest_rate,
        $psa_execution_deadline,
        $days_for_due_diligence,
        $days_for_exclusivity,
        $other_terms
    ): self {
        return (new self())->setProps(get_defined_vars());
    }

LetterOfIntent::dto(...

but then the class just started to feel dirty in my heart.

Please or to participate in this conversation.