0

I'm working on a project in which I'm using Spatie's laravel-data package (https://spatie.be/docs/laravel-data/v2/introduction) to manage Data Transfer Objects (DTOs). I've got some DTOs which are currently working fine until I got to this one in which I'm facing 2 issues:

1 So far I've been able to use the DTO as parameter in my controller and the request gets converted to the DTO correctly such as:

public function store(ProductData $data) : ProductsResource
    {
        $product = UpsertProductAction::execute($data);
        return new ProductsResource($product->load('category'));
    }

Now I've got a custom route for a customer entity. The route is define like this:

Route::post('customers/individuals', [ CustomersController::class, 'addIndividual' ]);

and the addIndividual method in my controller is:

public function addIndividual(IndividualData $data)
    {
        echo "here";
        print_r($data);
        dd("watup");
    }

but when I hit the route from my REST client I get nothing back. While debugging it doesn't seem to hit the controller. I get a 200 OK response and Laravel's main page and that's it.

But if I modify my method to be:

public function addIndividual(Request $request)
{
    
       $data = IndividualData::from($request->all());
       dd($data);
}

Now I see the dd being hit. What's going on here?

Here's my Data class:

<?php

namespace Domain\Customer\DataTransferObjects;

use Illuminate\Validation\Rule;
use Spatie\LaravelData\Data;

class IndividualData extends Data
{
    public function __construct(
        public readonly ?string $id,
        public readonly string $identification,
        public readonly string $identification_type,
        public readonly string $first_name,
        public readonly ?string $middle_name,
        public readonly string $last_name,
        public readonly ?string $second_last_name,
        public readonly ?string $primary_phone_number,
        public readonly ?string $primary_phone_number_type,
    ) {}

    public static function rules() : array
    {
        return [
            'identification' => [
                'required',
                'string',
            ],
            'identification_type' => [
                'required',
                'string',
            ],
            'first_name' => [
                'required',
                'string',
            ],
            'middle_name' => [
                'string',
                'nullable',
                'sometimes'
            ],
            'last_name' => [
                'required',
                'string',
            ],
            'second_last_name' => [
                'string',
                'nullable',
                'sometimes'
            ],
            'primary_phone_number' => [
                'string',
                'nullable',
                'sometimes'
            ],
            'primary_phone_number_type' => [
                'string',
                'nullable',
                'sometimes'
            ],
        ];
    }

}

2 The second question is related to the first one. So with previous methods such as

public function store(ProductData $data) : ProductsResource
    {
        $product = UpsertProductAction::execute($data);
        return new ProductsResource($product->load('category'));
    }

if there's some data that was declared as required in ProductData and not passed then I get a nice error back like:

{
    "message": "The barcode field is required. (and 1 more error)",
    "errors": {
        "barcode": [
            "The barcode field is required."
        ],
        "name": [
            "The name has already been taken."
        ]
    }
}

But now, if I simply use the request and inject it to the Data object: $data = IndividualData::from($request->all()); and there's required data missing from the request then I'm getting back a 500 error with a message such as:

Could not create `Domain\Customer\DataTransferObjects\IndividualData`: the constructor requires 9 parameters, 8 given.Parameters given: id, identification_type, first_name, middle_name, last_name, second_last_name, primary_phone_number, primary_phone_number_type.

If I wrapped in a try/catch statement the injection of the request to create the DTO:

try {
            $data = IndividualData::from($request->all());
            $dto = $data;
            $customer = UpsertCustomerAction::execute($data);

        } catch (\Exception $exception) {
            return $exception;
        }

then I'm getting:

ArgumentCountError: Domain\Customer\DataTransferObjects\IndividualData::__construct(): Argument #2 ($identification) not passed in /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/src/Domain/Customer/DataTransferObjects/IndividualData.php:10 Stack trace: #0 /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/vendor/spatie/laravel-data/src/Resolvers/DataFromArrayResolver.php(57): Domain\Customer\DataTransferObjects\IndividualData->__construct(NULL, NULL, 'CC', 'Inigo', NULL, 'Montoya', 'Arias', '6464654635', 'cel') #1 /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/vendor/spatie/laravel-data/src/Resolvers/DataFromArrayResolver.php(38): Spatie\LaravelData\Resolvers\DataFromArrayResolver->createData(Object(Spatie\LaravelData\Support\DataClass), Object(Illuminate\Support\Collection)) #2 /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(731): Spatie\LaravelData\Resolvers\DataFromArrayResolver->Spatie\LaravelData\Resolvers{closure}(Object(Illuminate\Support\Collection)) #3 /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/vendor/spatie/laravel-data/src/Resolvers/DataFromArrayResolver.php(38): Illuminate\Support\Collection->pipe(Object(Closure)) #4 /Users/hansgruber/Desktop/webdev/projects/dundermifflin-be/vendor/spatie/laravel-data/src/Resolvers/DataFromSomethingResolver.php(45): Spatie\LaravelData\Resolvers\DataFromArrayResolver->execute('Domain\Customer...', Object(Illuminate\Support\Collection)) #5

why I'm not longer getting the nicely formatted errors?

Thanks.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
MrCujo
  • 1,218
  • 3
  • 31
  • 56

1 Answers1

0

Spent hours looking around for this issue! It turns out I didn't have the following header set in my request in my REST client:

Accept: application/json

Worse thing of all is that all my other requests had it but most of them were a modified copy of one that had the header, I manually created the one that was failing reason why I had forgotten about it

MrCujo
  • 1,218
  • 3
  • 31
  • 56